mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-24 15:10:19 +00:00
Currency System Update (#448)
* initial update of currency system * WIP progress * Finish initial currency string error returns * fix whoopsie testing for non https insecureinos * Current WIP for getEnabledPairs check and error return * WIP continued * When getting enabled pairs throw error when item is not contained in available pairs list * More updates -WIP * Wip continued including potential interface * Current WIP * pairs manager pass * drop asset string and just use the map key, plus return some errors and create more work for myself. * clean and fixed a bug in currency.json, will not populate correctly without coinmarketcap api keys set. * purge logger references after merge * go mod tidy after merge * Pointer change WIP * fix some issues and added error returns to a few items (WIP) * WIP * Clean * Fix some linter issues * Fix more linter issues * even more linters * xtda nits * revert pointer change and rm field * Addr madcozbadd nits * fix linter issues: shadow declarations * Fix linter issues: gocritic huge things * linter issue fix * Addr nits * flush go mod files * after merge woops * fix shadow dec * Addr thrasher nits * addr nits * fix some issues * more fixes * RM println * Addr glorious nits * Add helper method for setting assets * add missing format directive * Addr nits * Actually process new futures contracts -_- derp * WIP for GRPC upgrade for pair management * update config pairs * finished disabling and enabling asset * linked update of tradable pairs to cli * fix oopsies * defer writing to file on program termination for currency storage system * update template * don't add disabled asset items to initial sync * Fix enable disabling a list of pairs and added in a slice error type so we can add whats allowable without throwing an error and return a report, also addressed some other nits * WIP on getting a channel to unsub * Wip track down unsubscribe bug and start creating streaming interface * purge websocket orderbook object and centralised updating routine for orderbook * general clean before interface implementation * stage one connection interface WIP * WIP * repackage wshandler WIP * find difference of subs and change signature of subscriber functions so we can batch subscriptions and unsubscription in exchanges that support it * design change on mange subscription routine WIP * integrated ZB with the new webosocket updates * WIP - okex conversion * integrate websocket upgrades for lakebtc, kraken, huobi, hitbtc, gateio, and WIP for coinbene * integrate another range of exchanges for websocket update * Added subscriber and unsubscriber methods to websocket functionality * fixed tests WIP * amalgamate cache setup with main websocket setup * reinstate exported fields traffic alert and shutdownC to accommodate gemini and lakebtc implementations * added in colon * Updated websocket auth handling as they werent getting passed through. Added a setter method for websocket URL due to the Binance generated auth key/listenKey. Fixed bug which stopped reconnection. * Fix subscribe candle bug Fix time conversion in candle Fix inititial candle history to datahandler Include funding to orderbook handling Include funding to trades Reduce code duplication in sub and unsub functions Added the ability to include funding currency websocket subscriptions validated all channels and added more items todo list (Auth items) * RM line * bitstamp pass * btcmarkets pass - still needs to implement unsubscriber functionality and pairs change test. * Batch outgoing subscriptions and fix unsubscribe bug * BTSE - bumped time to minute to reduce pinger calling by 75 calls per day. Fix authentication bug and add authentication pass into to-do. Batch outgoing subscription calls * fix type field and batch outgoing subs and unsubs for coinbasepro * Batch outgoing subs and unsubs * Fixes bug when matching return from authentication * Fix bug where params where being sent out of order due to map ,where depth items werent being subscribed too, where trying to subscribe to too many kline items caused error, where trying to get a nano secocond ID conflicted due to speed of generation. * Add websocket capability for currency pair change by utilizing full channel subscription list in subscribe function. * Add error handling * Fix public: time conversions, subscription list, stopped pushing heartbeat to data handler, aggregated list of connections. * hitBTC pass * returned nil instead of error due to period null bids and asks updates coming through. * Fix auth ping capture and reply. Added in interval handling for kline data. Added correct full trade data handling. Fix subscribe and unsubscribe. * Fix when websocket auth conn and token generation fail we don't try and auth sub. Fix bug between auth and normal connection id generation and matching. Batch outgoing payloads to increase efficiency. Updated matching functions to utilise channels instead of waitgroups and go routines. * RM debug output * rm func to get shutdown channel * Add unsubscriber functionality, added wsTicker type, removed return as this will impede data flow and cause reconnection when handling and processing data * okgroup WIP * *Added missing fields for websocket trades *Fix bug processing kline interval *Added fields for websocket ticker struct *Fix auth bug -Updated request and response matching param to interface so we can custom signature match. Stops auth subscribing before a reply is issued. -Updated channel inclusion of pair fo auth subs as this was missing. *Assortment of perfomance improvements * poloniex pass * send all trades to data handler, validated enabled and disable pairs * initial clean * centralised request matching mechanism * websocket main improvements WIP * WIP * Websocket management via gctcli WIP * GRPC expansion * Updated GCTCLI with websocket url and proxy setting functionality which flushes connection * Fix continuous spawning of routines bug on error with reconnection * Addr linter issues * fix subscription bug that I caused when I changed to a switch case * fix linter issue * fix woopsie * End of day WIP * Fix order submission REST, time conversion, order type conversion, orderID bugs * fix gateio test and unsubscribe bug * revert comment out code * websocketAPI changed to to true in configtest.json * fix race in gateio test * End of day WIP for websocket tests. * BugFix for binance when book isn't seeded. Updated websocket tests. Deprecated subscription manager. RM wrapper funcs. * Added string title to exchange name as they are saved as lower case in type, reinstated verbose check in websocket.go * Added verbosity check for setting websocket URL * fix bug where the asset had a mind of its own * purge dodgy coding * Fix tests, drop blocking chan in websocket Dial function * few more changes * race condition fix for websocket tests. * fix intermittent test failure due to underlying hash table storage * Address madcuzbad nit * RM superfluous printlines * Add quick top example with paramater fields * First pass Glorious nits * As per madcozbad suggestion return error when enabled pair not found in full return map. Add test. * addr madcozbadd nits * as per glorious suggestion rm'd loadedJSON field * adjusted ticker, added test and RM'd code that can never be executed * Addr nits and add in crypto rand genration for ID's * remove global channel declaration and rescoped as this was causing a lock * as per glorious suggestion restructured return error for websocket * addr glorious suggestions * fix linter issues * purge non-existent pair from testdata * add side field to struct and parse * addr glorious nits * Add verbosity to error returns and logs and fix string parsing in GCTRPC * fix speeling mistwake * Adds websocket functionality check before flushing websocket connection * Addr kraken panic and setting/flush websocket url stage one. * added websocket url check before setting with tests * Added in edge case test if by the time we call contains on available pairs it has been changed * remove error return for func * Continuation of tests * continuation of tests * Stop potential panic within pair creation function * Implement changes to upstream * rm sup comment * fix bug when subscribing and unsubscribing. Also add in boolean to determine there are currencies that need to be flushed via set pairs via gctcli * fix test * Fix linter issues * Fix tests * turn websocket off in config example * Fix issue where you cannot enable websocket when config is set to false, also added config websocket enable state saving * Introduced err var for same error returns * Add err var exchange base not found * restructure function * drop gctscript from generic response name * drop managesub delay const as its not being used * correctly implement websocket rate limiting for coinut * remove quotations * drop pair management check * fix spelling * return error in function to not update currency with unset role * amalagamted enable/disable into set function and added in pairstore fetch function * update error description * rm function * moved test function to sharedtestvals and move type to types.go * append delimiter onto currency delimiter strings * add test coverage * rm functions as they are set as methods in base * remove superfluous methods * Fix issue that would occur when a subscription errored and not appending successful subs * fix after rebase woopsie * fix linter issues * fix bug streamline code * fix linter issues * fix linter issues * fix case where it should not change ID if set but append new * fix whoopsie * fix websocket tests * fix readme, fix wrapper issues reporting template, go mod tidy * add test coverage * add test coverage and verified futures pariing * add in futures bypass as its not currently supported on BTSE until API update and implementation * removed downside/upside profit contract type as its no longer supported. Added in check in set config pairs to warn user of potential conflict and to manually remove or update. * If asset enabled add pair and increase code coverage * remove strings.title, set and fetch with strings.Lower but keep struct field exchangename unchanged. Streamline ticker and orderbook code. * Add code coverage * log error if setting default currency fails, add code coverage * address glorious nits * Addr xtda nits * fix linter issues * addr glorious nits * xtda nits * Addr glorious nits * add subscription protection and removed a superfluous wait call * fix test * fix whoopsie * addr xtda nits * addr glorious nits * Added asset types to subscriptions structs, also added in error handling for resubscription errors * consolidated rpc returned type and added in sucessful strings * dropped stream timing down to 100ms * DOC changes * proxy and url usage string additions * WIP * go mod tidy rides again * Addr nits * Addr nits, fix tests * fix wording * add in test case for currency matching * Add byte length check on outbound websocket payload subscriptions * addr thrasher nits * Addr madcozbadd nits * addr linter issues * Addr glorious nits by amalgamating function into one mega amazing function. * fix futures account subscription bug * addr glorious nits and reinstated wg.Wait() checks * changed string to currency delimiter string and setconnected by function
This commit is contained in:
@@ -141,14 +141,14 @@ Binaries will be published once the codebase reaches a stable condition.
|
||||
|
||||
|User|Contribution Amount|
|
||||
|--|--|
|
||||
| [thrasher-](https://github.com/thrasher-) | 640 |
|
||||
| [thrasher-](https://github.com/thrasher-) | 641 |
|
||||
| [shazbert](https://github.com/shazbert) | 191 |
|
||||
| [gloriousCode](https://github.com/gloriousCode) | 169 |
|
||||
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 48 |
|
||||
| [xtda](https://github.com/xtda) | 43 |
|
||||
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 51 |
|
||||
| [xtda](https://github.com/xtda) | 45 |
|
||||
| [ermalguni](https://github.com/ermalguni) | 14 |
|
||||
| [vadimzhukck](https://github.com/vadimzhukck) | 10 |
|
||||
| [MadCozBadd](https://github.com/MadCozBadd) | 8 |
|
||||
| [MadCozBadd](https://github.com/MadCozBadd) | 9 |
|
||||
| [140am](https://github.com/140am) | 8 |
|
||||
| [marcofranssen](https://github.com/marcofranssen) | 8 |
|
||||
| [dackroyd](https://github.com/dackroyd) | 5 |
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -49,9 +50,9 @@ func main() {
|
||||
|
||||
if !config.ConfirmECS(fileData) && !encrypt {
|
||||
var result interface{}
|
||||
errf := config.ConfirmConfigJSON(fileData, result)
|
||||
errf := json.Unmarshal(fileData, &result)
|
||||
if errf != nil {
|
||||
log.Fatal("File isn't in JSON format")
|
||||
log.Fatal(errf)
|
||||
}
|
||||
log.Println("File is already decrypted. Encrypting..")
|
||||
encrypt = true
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -147,9 +146,6 @@ func makeExchange(exch *exchange) error {
|
||||
newExchConfig.API.Credentials.Key = "Key"
|
||||
newExchConfig.API.Credentials.Secret = "Secret"
|
||||
newExchConfig.CurrencyPairs = ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -30,13 +30,10 @@ func ({{.Variable}} *{{.CapitalName}}) GetDefaultConfig() (*config.ExchangeConfi
|
||||
exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout
|
||||
exchCfg.BaseCurrencies = {{.Variable}}.BaseCurrencies
|
||||
|
||||
err := {{.Variable}}.SetupDefaults(exchCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
{{.Variable}}.SetupDefaults(exchCfg)
|
||||
|
||||
if {{.Variable}}.Features.Supports.RESTCapabilities.AutoPairUpdates {
|
||||
err = {{.Variable}}.UpdateTradablePairs(true)
|
||||
err := {{.Variable}}.UpdateTradablePairs(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -51,20 +48,46 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() {
|
||||
{{.Variable}}.Verbose = true
|
||||
{{.Variable}}.API.CredentialsValidator.RequiresKey = true
|
||||
{{.Variable}}.API.CredentialsValidator.RequiresSecret = true
|
||||
{{.Variable}}.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
},
|
||||
|
||||
// If using only one pair format for request and configuration, across all
|
||||
// supported asset types either SPOT and FUTURES etc. You can use the
|
||||
// example below:
|
||||
|
||||
// Request format denotes what the pair as a string will be, when you send
|
||||
// a request to an exchange.
|
||||
requestFmt := ¤cy.PairFormat{/*Set pair request formatting details here for e.g.*/ Uppercase: true, Delimiter: ":"}
|
||||
// Config format denotes what the pair as a string will be, when saved to
|
||||
// the config.json file.
|
||||
configFmt := ¤cy.PairFormat{/*Set pair request formatting details here*/}
|
||||
err := {{.Variable}}.SetGlobalPairsManager(requestFmt, configFmt, /*multiple assets can be set here using the asset package ie asset.Spot*/)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
// If assets require multiple differences in formating for request and
|
||||
// configuration, another exchange method can be be used e.g. futures
|
||||
// contracts require a dash as a delimiter rather than an underscore. You
|
||||
// can use this example below:
|
||||
|
||||
fmt1 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
}
|
||||
|
||||
fmt2 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: ":"},
|
||||
}
|
||||
|
||||
err = {{.Variable}}.StoreAssetPairFormat(asset.Spot, fmt1)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
err = {{.Variable}}.StoreAssetPairFormat(asset.Margin, fmt2)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
// Fill out the capabilities/features that the exchange supports
|
||||
{{.Variable}}.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
@@ -91,7 +114,7 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() {
|
||||
|
||||
{{.Variable}}.API.Endpoints.URLDefault = {{.Name}}APIURL
|
||||
{{.Variable}}.API.Endpoints.URL = {{.Variable}}.API.Endpoints.URLDefault
|
||||
{{.Variable}}.Websocket = wshandler.New()
|
||||
{{.Variable}}.Websocket = stream.New()
|
||||
{{.Variable}}.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
{{.Variable}}.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
{{.Variable}}.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
@@ -104,15 +127,12 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) error
|
||||
return nil
|
||||
}
|
||||
|
||||
err := {{.Variable}}.SetupDefaults(exch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
{{.Variable}}.SetupDefaults(exch)
|
||||
|
||||
// If websocket is supported, please fill out the following
|
||||
/*
|
||||
err = {{.Variable}}.Websocket.Setup(
|
||||
&wshandler.WebsocketSetup{
|
||||
&stream.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
@@ -129,7 +149,7 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) error
|
||||
return err
|
||||
}
|
||||
|
||||
{{.Variable}}.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
{{.Variable}}.WebsocketConn = &stream.WebsocketConnection{
|
||||
ExchangeName: {{.Variable}}.Name,
|
||||
URL: {{.Variable}}.Websocket.GetWebsocketURL(),
|
||||
ProxyURL: {{.Variable}}.Websocket.GetProxyAddress(),
|
||||
@@ -195,8 +215,13 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateTradablePairs(forceUpdate bool) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return {{.Variable}}.UpdatePairs(currency.NewPairsFromStrings(pairs),
|
||||
asset.Spot, false, forceUpdate)
|
||||
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return {{.Variable}}.UpdatePairs(p, asset.Spot, false, forceUpdate)
|
||||
}
|
||||
|
||||
|
||||
@@ -356,11 +381,6 @@ func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFundsToInternationalBank(with
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
@@ -377,30 +397,6 @@ func ({{.Variable}} *{{.CapitalName}}) GetFeeByType(feeBuilder *exchange.FeeBuil
|
||||
return 0, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func ({{.Variable}} *{{.CapitalName}}) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
{{.Variable}}.Websocket.SubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
{{.Variable}}.Websocket.RemoveSubscribedChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func ({{.Variable}} *{{.CapitalName}}) AuthenticateWebsocket() error {
|
||||
return common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
func ({{.Variable}} *{{.CapitalName}}) ValidateCredentials() error {
|
||||
_, err := {{.Variable}}.UpdateAccountInfo()
|
||||
|
||||
@@ -296,10 +296,16 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
|
||||
switch {
|
||||
case currencyPairOverride != "":
|
||||
p = currency.NewPairFromString(currencyPairOverride)
|
||||
var err error
|
||||
p, err = currency.NewPairFromString(currencyPairOverride)
|
||||
if err != nil {
|
||||
log.Printf("%v Encountered error: '%v'", base.GetName(), err)
|
||||
continue
|
||||
}
|
||||
case len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled) == 0:
|
||||
if len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available) == 0 {
|
||||
log.Printf("%v has no enabled or available currencies. Skipping", base.GetName())
|
||||
log.Printf("%v has no enabled or available currencies. Skipping",
|
||||
base.GetName())
|
||||
continue
|
||||
}
|
||||
p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available.GetRandomPair()
|
||||
@@ -308,8 +314,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
}
|
||||
|
||||
responseContainer := ExchangeAssetPairResponses{
|
||||
AssetType: assetTypes[i],
|
||||
CurrencyPair: p,
|
||||
AssetType: assetTypes[i],
|
||||
Pair: p,
|
||||
}
|
||||
|
||||
log.Printf("Setup config for %v %v %v", base.GetName(), assetTypes[i], p)
|
||||
@@ -858,7 +864,7 @@ func outputToConsole(exchangeResponses []ExchangeResponses) {
|
||||
log.Printf("%v Result: %v", exchangeResponses[i].ExchangeName, k)
|
||||
log.Printf("Function:\t%v", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Function)
|
||||
log.Printf("AssetType:\t%v", exchangeResponses[i].AssetPairResponses[j].AssetType)
|
||||
log.Printf("Currency:\t%v\n", exchangeResponses[i].AssetPairResponses[j].CurrencyPair)
|
||||
log.Printf("Currency:\t%v\n", exchangeResponses[i].AssetPairResponses[j].Pair)
|
||||
log.Printf("Wrapper Params:\t%s\n", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].SentParams)
|
||||
if exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Error != "" {
|
||||
totalErrors++
|
||||
|
||||
@@ -69,10 +69,10 @@
|
||||
{{ if eq $length 1 }}
|
||||
{{ else }}
|
||||
<div class="col-11 container-fluid">
|
||||
<h4> {{ $assetPairResponses.AssetType }} {{ $assetPairResponses.CurrencyPair }} Results </h4>
|
||||
<h4> {{ $assetPairResponses.AssetType }} {{ $assetPairResponses.Pair}} Results </h4>
|
||||
<div class="alert col-6 container-fluid {{ if $assetPairResponses.ErrorCount }} alert-danger {{ else }} alert-success {{ end }}"
|
||||
role="alert">
|
||||
{{ $assetPairResponses.AssetType }} {{ $assetPairResponses.CurrencyPair }} Total Errors:
|
||||
{{ $assetPairResponses.AssetType }} {{ $assetPairResponses.Pair}} Total Errors:
|
||||
{{ $assetPairResponses.ErrorCount }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,18 +108,18 @@
|
||||
{{ $assetPairResponses.AssetType }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $assetPairResponses.CurrencyPair }}
|
||||
{{ $assetPairResponses.Pair}}
|
||||
</td>
|
||||
<td>
|
||||
{{ $endpointResponse.Function }}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info" type="button" data-toggle="collapse"
|
||||
data-target="#{{ $assetPairResponses.AssetType }}{{ $assetPairResponses.CurrencyPair }}{{ $endpointResponse.Function }}sent"
|
||||
data-target="#{{ $assetPairResponses.AssetType }}{{ $assetPairResponses.Pair}}{{ $endpointResponse.Function }}sent"
|
||||
aria-expanded="false" aria-controls="collapseExample">
|
||||
Wrapper Params
|
||||
</button>
|
||||
<div id="{{ $assetPairResponses.AssetType }}{{ $assetPairResponses.CurrencyPair }}{{ $endpointResponse.Function }}sent"
|
||||
<div id="{{ $assetPairResponses.AssetType }}{{ $assetPairResponses.Pair}}{{ $endpointResponse.Function }}sent"
|
||||
class="collapse">
|
||||
<div class="card card-body" style="word-wrap:anywhere">
|
||||
{{ $endpointResponse.SentParams | printf "%s" }}
|
||||
@@ -131,11 +131,11 @@
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info" type="button" data-toggle="collapse"
|
||||
data-target="#{{ $assetPairResponses.AssetType }}{{ $assetPairResponses.CurrencyPair }}{{ $endpointResponse.Function }}"
|
||||
data-target="#{{ $assetPairResponses.AssetType }}{{ $assetPairResponses.Pair}}{{ $endpointResponse.Function }}"
|
||||
aria-expanded="false" aria-controls="collapseExample">
|
||||
Wrapper Response
|
||||
</button>
|
||||
<div id="{{ $assetPairResponses.AssetType }}{{ $assetPairResponses.CurrencyPair }}{{ $endpointResponse.Function }}"
|
||||
<div id="{{ $assetPairResponses.AssetType }}{{ $assetPairResponses.Pair}}{{ $endpointResponse.Function }}"
|
||||
class="collapse">
|
||||
<div class="card card-body" style="word-wrap:anywhere">
|
||||
{{ $endpointResponse.Response | printf "%s" }}
|
||||
|
||||
@@ -58,7 +58,7 @@ type ExchangeResponses struct {
|
||||
type ExchangeAssetPairResponses struct {
|
||||
ErrorCount int64 `json:"errorCount"`
|
||||
AssetType asset.Item `json:"asset"`
|
||||
CurrencyPair currency.Pair `json:"currency"`
|
||||
Pair currency.Pair `json:"currency"`
|
||||
EndpointResponses []EndpointResponse `json:"responses"`
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -566,7 +564,11 @@ func getTicker(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.GetTicker(context.Background(),
|
||||
&gctrpc.GetTickerRequest{
|
||||
@@ -679,7 +681,11 @@ func getOrderbook(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.GetOrderbook(context.Background(),
|
||||
&gctrpc.GetOrderbookRequest{
|
||||
@@ -1199,7 +1205,11 @@ func getOrders(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.GetOrders(context.Background(), &gctrpc.GetOrdersRequest{
|
||||
Exchange: exchangeName,
|
||||
@@ -1407,7 +1417,11 @@ func submitOrder(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.SubmitOrder(context.Background(), &gctrpc.SubmitOrderRequest{
|
||||
Exchange: exchangeName,
|
||||
@@ -1516,7 +1530,11 @@ func simulateOrder(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.SimulateOrder(context.Background(), &gctrpc.SimulateOrderRequest{
|
||||
Exchange: exchangeName,
|
||||
@@ -1618,7 +1636,11 @@ func whaleBomb(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.WhaleBomb(context.Background(), &gctrpc.WhaleBombRequest{
|
||||
Exchange: exchangeName,
|
||||
@@ -1750,7 +1772,11 @@ func cancelOrder(c *cli.Context) error {
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
p = currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
var err error
|
||||
p, err = currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
@@ -1985,7 +2011,11 @@ func addEvent(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.AddEvent(context.Background(), &gctrpc.AddEventRequest{
|
||||
Exchange: exchangeName,
|
||||
@@ -2776,249 +2806,6 @@ func setLoggerDetails(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var getExchangePairsCommand = cli.Command{
|
||||
Name: "getexchangepairs",
|
||||
Usage: "gets an exchanges supported currency pairs (available and enabled) plus asset types",
|
||||
ArgsUsage: "<exchange> <asset>",
|
||||
Action: getExchangePairs,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to list of the currency pairs of",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "the asset type to filter by",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getExchangePairs(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
cli.ShowCommandHelp(c, "getexchangepairs")
|
||||
return nil
|
||||
}
|
||||
|
||||
var exchange string
|
||||
var asset string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchange = c.String("exchange")
|
||||
} else {
|
||||
exchange = c.Args().First()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
asset = c.String("asset")
|
||||
} else {
|
||||
asset = c.Args().Get(1)
|
||||
}
|
||||
|
||||
asset = strings.ToLower(asset)
|
||||
if !validAsset(asset) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.GetExchangePairs(context.Background(),
|
||||
&gctrpc.GetExchangePairsRequest{
|
||||
Exchange: exchange,
|
||||
Asset: asset,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var enableExchangePairCommand = cli.Command{
|
||||
Name: "enableexchangepair",
|
||||
Usage: "enables an exchange currency pair",
|
||||
ArgsUsage: "<exchange> <pair> <asset>",
|
||||
Action: enableExchangePair,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to enable the currency pair for",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pair",
|
||||
Usage: "the currency pair to enable",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "the asset type to enable the currency pair for",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func enableExchangePair(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
cli.ShowCommandHelp(c, "enableexchangepair")
|
||||
return nil
|
||||
}
|
||||
|
||||
var exchange string
|
||||
var pair string
|
||||
var asset string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchange = c.String("exchange")
|
||||
} else {
|
||||
exchange = c.Args().First()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
if c.IsSet("pair") {
|
||||
pair = c.String("pair")
|
||||
} else {
|
||||
pair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(pair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
asset = c.String("asset")
|
||||
} else {
|
||||
asset = c.Args().Get(2)
|
||||
}
|
||||
|
||||
asset = strings.ToLower(asset)
|
||||
if !validAsset(asset) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(pair, pairDelimiter)
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.EnableExchangePair(context.Background(),
|
||||
&gctrpc.ExchangePairRequest{
|
||||
Exchange: exchange,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
AssetType: asset,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var disableExchangePairCommand = cli.Command{
|
||||
Name: "disableexchangepair",
|
||||
Usage: "disables a previously enabled exchange currency pair",
|
||||
ArgsUsage: "<exchange> <pair> <asset>",
|
||||
Action: disableExchangePair,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to disable the currency pair for",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pair",
|
||||
Usage: "the currency pair to disable",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "the asset type to disable the currency pair for",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func disableExchangePair(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
cli.ShowCommandHelp(c, "disableexchangepair")
|
||||
return nil
|
||||
}
|
||||
|
||||
var exchange string
|
||||
var pair string
|
||||
var asset string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchange = c.String("exchange")
|
||||
} else {
|
||||
exchange = c.Args().First()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
if c.IsSet("pair") {
|
||||
pair = c.String("pair")
|
||||
} else {
|
||||
pair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(pair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
asset = c.String("asset")
|
||||
} else {
|
||||
asset = c.Args().Get(2)
|
||||
}
|
||||
|
||||
asset = strings.ToLower(asset)
|
||||
if !validAsset(asset) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(pair, pairDelimiter)
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.DisableExchangePair(context.Background(),
|
||||
&gctrpc.ExchangePairRequest{
|
||||
Exchange: exchange,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
AssetType: asset,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var getOrderbookStreamCommand = cli.Command{
|
||||
Name: "getorderbookstream",
|
||||
Usage: "gets the orderbook stream for a specific currency pair and exchange",
|
||||
@@ -3088,7 +2875,10 @@ func getOrderbookStream(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(pair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.GetOrderbookStream(context.Background(),
|
||||
@@ -3296,7 +3086,10 @@ func getTickerStream(c *cli.Context) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
p := currency.NewPairDelimiter(pair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.GetTickerStream(context.Background(),
|
||||
@@ -3410,19 +3203,6 @@ func getExchangeTickerStream(c *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func clearScreen() error {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd := exec.Command("cmd", "/c", "cls")
|
||||
cmd.Stdout = os.Stdout
|
||||
return cmd.Run()
|
||||
default:
|
||||
cmd := exec.Command("clear")
|
||||
cmd.Stdout = os.Stdout
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
var getAuditEventCommand = cli.Command{
|
||||
Name: "getauditevent",
|
||||
Usage: "gets audit events matching query parameters",
|
||||
@@ -3529,8 +3309,8 @@ func getAuditEvent(c *cli.Context) error {
|
||||
|
||||
var uuid, filename, path string
|
||||
var gctScriptCommand = cli.Command{
|
||||
Name: "gctscript",
|
||||
Usage: "execute gctscript command",
|
||||
Name: "script",
|
||||
Usage: "execute scripting management command",
|
||||
ArgsUsage: "<command> <args>",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
@@ -4022,7 +3802,10 @@ func getHistoricCandles(c *cli.Context) error {
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var assetType string
|
||||
if c.IsSet("asset") {
|
||||
@@ -4038,7 +3821,6 @@ func getHistoricCandles(c *cli.Context) error {
|
||||
if c.IsSet("rangesize") {
|
||||
candleRangeSize = c.Int64("rangesize")
|
||||
} else if c.Args().Get(3) != "" {
|
||||
var err error
|
||||
candleRangeSize, err = strconv.ParseInt(c.Args().Get(3), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -4048,7 +3830,6 @@ func getHistoricCandles(c *cli.Context) error {
|
||||
if c.IsSet("granularity") {
|
||||
candleGranularity = c.Int64("granularity")
|
||||
} else if c.Args().Get(4) != "" {
|
||||
var err error
|
||||
candleGranularity, err = strconv.ParseInt(c.Args().Get(4), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -4151,7 +3932,11 @@ func getHistoricCandlesExtended(c *cli.Context) error {
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var assetType string
|
||||
if c.IsSet("asset") {
|
||||
@@ -4167,7 +3952,6 @@ func getHistoricCandlesExtended(c *cli.Context) error {
|
||||
if c.IsSet("interval") {
|
||||
candleGranularity = c.Int64("interval")
|
||||
} else if c.Args().Get(3) != "" {
|
||||
var err error
|
||||
candleGranularity, err = strconv.ParseInt(c.Args().Get(3), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
20
cmd/gctcli/helpers.go
Normal file
20
cmd/gctcli/helpers.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func clearScreen() error {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd := exec.Command("cmd", "/c", "cls")
|
||||
cmd.Stdout = os.Stdout
|
||||
return cmd.Run()
|
||||
default:
|
||||
cmd := exec.Command("clear")
|
||||
cmd.Stdout = os.Stdout
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
@@ -127,9 +127,7 @@ func main() {
|
||||
withdrawalRequestCommand,
|
||||
getLoggerDetailsCommand,
|
||||
setLoggerDetailsCommand,
|
||||
getExchangePairsCommand,
|
||||
enableExchangePairCommand,
|
||||
disableExchangePairCommand,
|
||||
exchangePairManagerCommand,
|
||||
getOrderbookStreamCommand,
|
||||
getExchangeOrderbookStreamCommand,
|
||||
getTickerStreamCommand,
|
||||
@@ -138,6 +136,7 @@ func main() {
|
||||
getHistoricCandlesCommand,
|
||||
getHistoricCandlesExtendedCommand,
|
||||
gctScriptCommand,
|
||||
websocketManagerCommand,
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
|
||||
456
cmd/gctcli/pair_management.go
Normal file
456
cmd/gctcli/pair_management.go
Normal file
@@ -0,0 +1,456 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/gctrpc"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var exchangePairManagerCommand = cli.Command{
|
||||
Name: "pair",
|
||||
Usage: "execute exchange pair management command",
|
||||
ArgsUsage: "<command> <args>",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "get",
|
||||
Usage: "returns all enabled and available pairs by asset type",
|
||||
ArgsUsage: "<asset>",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "asset",
|
||||
},
|
||||
},
|
||||
Action: getExchangePairs,
|
||||
},
|
||||
{
|
||||
Name: "disableasset",
|
||||
Usage: "disables asset type",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "asset",
|
||||
},
|
||||
},
|
||||
Action: enableDisableExchangeAsset,
|
||||
},
|
||||
{
|
||||
Name: "enableasset",
|
||||
Usage: "enables asset type",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "asset",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "enable",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
Action: enableDisableExchangeAsset,
|
||||
},
|
||||
{
|
||||
Name: "disable",
|
||||
Usage: "disable pairs by asset type",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pairs",
|
||||
Usage: "either a single currency pair string or comma delimiter string of pairs e.g. \"BTC-USD,XRP-USD\"",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "asset",
|
||||
},
|
||||
},
|
||||
Action: enableDisableExchangePair,
|
||||
},
|
||||
{
|
||||
Name: "enable",
|
||||
Usage: "enable pairs by asset type",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pairs",
|
||||
Usage: "either a single currency pair string or comma delimiter string of pairs e.g. \"BTC-USD,XRP-USD\"",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "asset",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "enable",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
Action: enableDisableExchangePair,
|
||||
},
|
||||
{
|
||||
Name: "enableall",
|
||||
Usage: "enable all pairs",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "enable",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
Action: enableDisableAllExchangePairs,
|
||||
},
|
||||
{
|
||||
Name: "disableall",
|
||||
Usage: "dissable all pairs",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
},
|
||||
Action: enableDisableAllExchangePairs,
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Usage: "fetches supported pairs from the exchange and updates available pairs and removes unsupported enable pairs",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
},
|
||||
Action: updateExchangeSupportedPairs,
|
||||
},
|
||||
{
|
||||
Name: "getassets",
|
||||
Usage: "fetches supported assets",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
},
|
||||
Action: getExchangeAssets,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func enableDisableExchangePair(c *cli.Context) error {
|
||||
enable := c.BoolT("enable")
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
if enable {
|
||||
return cli.ShowCommandHelp(c, "enable")
|
||||
}
|
||||
|
||||
return cli.ShowCommandHelp(c, "disable")
|
||||
}
|
||||
|
||||
var exchange string
|
||||
var pairs string
|
||||
var asset string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchange = c.String("exchange")
|
||||
} else {
|
||||
exchange = c.Args().First()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
if c.IsSet("pairs") {
|
||||
pairs = c.String("pairs")
|
||||
} else {
|
||||
pairs = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
asset = c.String("asset")
|
||||
} else {
|
||||
asset = c.Args().Get(2)
|
||||
}
|
||||
|
||||
asset = strings.ToLower(asset)
|
||||
if !validAsset(asset) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
pairList := strings.Split(pairs, ",")
|
||||
|
||||
var validPairs []*gctrpc.CurrencyPair
|
||||
for i := range pairList {
|
||||
if !validPair(pairList[i]) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
p, err := currency.NewPairFromString(pairList[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
validPairs = append(validPairs, &gctrpc.CurrencyPair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
})
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
|
||||
result, err := client.SetExchangePair(context.Background(),
|
||||
&gctrpc.SetExchangePairRequest{
|
||||
Exchange: exchange,
|
||||
Pairs: validPairs,
|
||||
AssetType: asset,
|
||||
Enable: enable,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExchangePairs(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowSubcommandHelp(c)
|
||||
}
|
||||
|
||||
var exchange string
|
||||
var asset string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchange = c.String("exchange")
|
||||
} else {
|
||||
exchange = c.Args().First()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
asset = c.String("asset")
|
||||
} else {
|
||||
asset = c.Args().Get(1)
|
||||
}
|
||||
|
||||
asset = strings.ToLower(asset)
|
||||
if !validAsset(asset) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.GetExchangePairs(context.Background(),
|
||||
&gctrpc.GetExchangePairsRequest{
|
||||
Exchange: exchange,
|
||||
Asset: asset,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableDisableExchangeAsset(c *cli.Context) error {
|
||||
enable := c.BoolT("enable")
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
if enable {
|
||||
return cli.ShowCommandHelp(c, "enableasset")
|
||||
}
|
||||
return cli.ShowCommandHelp(c, "disableasset")
|
||||
}
|
||||
|
||||
var exchange string
|
||||
var asset string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchange = c.String("exchange")
|
||||
} else {
|
||||
exchange = c.Args().First()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
asset = c.String("asset")
|
||||
} else {
|
||||
asset = c.Args().Get(1)
|
||||
}
|
||||
|
||||
asset = strings.ToLower(asset)
|
||||
if !validAsset(asset) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.SetExchangeAsset(context.Background(),
|
||||
&gctrpc.SetExchangeAssetRequest{
|
||||
Exchange: exchange,
|
||||
Asset: asset,
|
||||
Enable: enable,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableDisableAllExchangePairs(c *cli.Context) error {
|
||||
enable := c.BoolT("enable")
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
if enable {
|
||||
return cli.ShowCommandHelp(c, "enableall")
|
||||
}
|
||||
return cli.ShowCommandHelp(c, "disableall")
|
||||
}
|
||||
|
||||
var exchange string
|
||||
if c.IsSet("exchange") {
|
||||
exchange = c.String("exchange")
|
||||
} else {
|
||||
exchange = c.Args().First()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.SetAllExchangePairs(context.Background(),
|
||||
&gctrpc.SetExchangeAllPairsRequest{
|
||||
Exchange: exchange,
|
||||
Enable: enable,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateExchangeSupportedPairs(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()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.UpdateExchangeSupportedPairs(context.Background(),
|
||||
&gctrpc.UpdateExchangeSupportedPairsRequest{
|
||||
Exchange: exchange,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExchangeAssets(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()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return errInvalidExchange
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.GetExchangeAssets(context.Background(),
|
||||
&gctrpc.GetExchangeAssetsRequest{
|
||||
Exchange: exchange,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
270
cmd/gctcli/websocket_management.go
Normal file
270
cmd/gctcli/websocket_management.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/gctrpc"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var websocketManagerCommand = cli.Command{
|
||||
Name: "websocket",
|
||||
Usage: "execute websocket management command",
|
||||
ArgsUsage: "<command> <args>",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "getinfo",
|
||||
Usage: "returns all exchange websocket information",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
},
|
||||
Action: getwebsocketInfo,
|
||||
},
|
||||
{
|
||||
Name: "disable",
|
||||
Usage: "disables websocket connection for an exchange",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
},
|
||||
Action: enableDisableWebsocket,
|
||||
},
|
||||
{
|
||||
Name: "enable",
|
||||
Usage: "enables websocket connection for an exchange",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "enable",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
Action: enableDisableWebsocket,
|
||||
},
|
||||
{
|
||||
Name: "getsubs",
|
||||
Usage: "returns current subscriptions for an exchange",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
},
|
||||
Action: getSubscriptions,
|
||||
},
|
||||
{
|
||||
Name: "setproxy",
|
||||
Usage: "sets exchange websocket proxy, flushes and reroutes connection",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "proxy",
|
||||
Usage: "proxy address to change to, if proxy string is not set, this will stop the utilization of the prior set proxy.",
|
||||
},
|
||||
},
|
||||
Action: setProxy,
|
||||
},
|
||||
{
|
||||
Name: "seturl",
|
||||
Usage: "sets exchange websocket endpoint URL and resets the websocket connection",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to act on",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "url",
|
||||
Usage: "url string to change to, an empty string will set it back to the packaged defined default",
|
||||
},
|
||||
},
|
||||
Action: setURL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getwebsocketInfo(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()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return fmt.Errorf("[%s] is not a valid exchange", exchange)
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.WebsocketGetInfo(context.Background(),
|
||||
&gctrpc.WebsocketGetInfoRequest{Exchange: exchange})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableDisableWebsocket(c *cli.Context) error {
|
||||
enable := c.BoolT("enable")
|
||||
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()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return fmt.Errorf("[%s] is not a valid exchange", exchange)
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.WebsocketSetEnabled(context.Background(),
|
||||
&gctrpc.WebsocketSetEnabledRequest{Exchange: exchange, Enable: enable})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSubscriptions(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()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return fmt.Errorf("[%s] is not a valid exchange", exchange)
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.WebsocketGetSubscriptions(context.Background(),
|
||||
&gctrpc.WebsocketGetSubscriptionsRequest{Exchange: exchange})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setProxy(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()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return fmt.Errorf("[%s] is not a valid exchange", exchange)
|
||||
}
|
||||
|
||||
var proxy string
|
||||
if c.IsSet("proxy") {
|
||||
proxy = c.String("proxy")
|
||||
} else {
|
||||
proxy = c.Args().Get(1)
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.WebsocketSetProxy(context.Background(),
|
||||
&gctrpc.WebsocketSetProxyRequest{Exchange: exchange, Proxy: proxy})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setURL(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()
|
||||
}
|
||||
|
||||
if !validExchange(exchange) {
|
||||
return fmt.Errorf("[%s] is not a valid exchange", exchange)
|
||||
}
|
||||
|
||||
var url string
|
||||
if c.IsSet("url") {
|
||||
url = c.String("url")
|
||||
} else {
|
||||
url = c.Args().Get(1)
|
||||
}
|
||||
|
||||
conn, err := setupClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderClient(conn)
|
||||
result, err := client.WebsocketSetURL(context.Background(),
|
||||
&gctrpc.WebsocketSetURLRequest{Exchange: exchange, Url: url})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
@@ -361,3 +361,18 @@ func InArray(val, array interface{}) (exists bool, index int) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Errors defines multiple errors
|
||||
type Errors []error
|
||||
|
||||
// Error implements error interface
|
||||
func (e Errors) Error() string {
|
||||
if len(e) == 0 {
|
||||
return ""
|
||||
}
|
||||
var r string
|
||||
for i := range e {
|
||||
r += e[i].Error() + ", "
|
||||
}
|
||||
return r[:len(r)-2]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -547,3 +548,18 @@ func TestInArray(t *testing.T) {
|
||||
t.Errorf("found a non existent value in the slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
var test Errors
|
||||
if test.Error() != "" {
|
||||
t.Fatal("string should be nil")
|
||||
}
|
||||
test = append(test, errors.New("test1"))
|
||||
if test.Error() != "test1" {
|
||||
t.Fatal("does not match error")
|
||||
}
|
||||
test = append(test, errors.New("test2"))
|
||||
if test.Error() != "test1, test2" {
|
||||
t.Fatal("does not match error")
|
||||
}
|
||||
}
|
||||
|
||||
464
config/config.go
464
config/config.go
@@ -345,71 +345,46 @@ func (c *Config) GetExchangeAssetTypes(exchName string) (asset.Items, error) {
|
||||
return nil, fmt.Errorf("exchange %s currency pairs is nil", exchName)
|
||||
}
|
||||
|
||||
return exchCfg.CurrencyPairs.AssetTypes, nil
|
||||
return exchCfg.CurrencyPairs.GetAssetTypes(), nil
|
||||
}
|
||||
|
||||
// SupportsExchangeAssetType returns whether or not the exchange supports the supplied asset type
|
||||
func (c *Config) SupportsExchangeAssetType(exchName string, assetType asset.Item) (bool, error) {
|
||||
func (c *Config) SupportsExchangeAssetType(exchName string, assetType asset.Item) error {
|
||||
exchCfg, err := c.GetExchangeConfig(exchName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
if exchCfg.CurrencyPairs == nil {
|
||||
return false, fmt.Errorf("exchange %s currency pairs is nil", exchName)
|
||||
return fmt.Errorf("exchange %s currency pairs is nil", exchName)
|
||||
}
|
||||
|
||||
if !asset.IsValid(assetType) {
|
||||
return false, fmt.Errorf("exchange %s invalid asset types", exchName)
|
||||
return fmt.Errorf("exchange %s invalid asset type %s",
|
||||
exchName,
|
||||
assetType)
|
||||
}
|
||||
|
||||
return exchCfg.CurrencyPairs.AssetTypes.Contains(assetType), nil
|
||||
}
|
||||
|
||||
// CheckExchangeAssetsConsistency checks the exchanges supported assets compared to the stored
|
||||
// entries and removes any non supported
|
||||
func (c *Config) CheckExchangeAssetsConsistency(exchName string) {
|
||||
exchCfg, err := c.GetExchangeConfig(exchName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
exchangeAssetTypes, err := c.GetExchangeAssetTypes(exchName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
storedAssetTypes := exchCfg.CurrencyPairs.GetAssetTypes()
|
||||
for x := range storedAssetTypes {
|
||||
if !exchangeAssetTypes.Contains(storedAssetTypes[x]) {
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"%s has non-needed stored asset type %v. Removing..\n",
|
||||
exchName, storedAssetTypes[x])
|
||||
exchCfg.CurrencyPairs.Delete(storedAssetTypes[x])
|
||||
}
|
||||
if !exchCfg.CurrencyPairs.GetAssetTypes().Contains(assetType) {
|
||||
return fmt.Errorf("exchange %s unsupported asset type %s",
|
||||
exchName,
|
||||
assetType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPairs sets the exchanges currency pairs
|
||||
func (c *Config) SetPairs(exchName string, assetType asset.Item, enabled bool, pairs currency.Pairs) error {
|
||||
if len(pairs) == 0 {
|
||||
return fmt.Errorf("pairs is nil")
|
||||
}
|
||||
|
||||
exchCfg, err := c.GetExchangeConfig(exchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
supports, err := c.SupportsExchangeAssetType(exchName, assetType)
|
||||
err = c.SupportsExchangeAssetType(exchName, assetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !supports {
|
||||
return fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType)
|
||||
}
|
||||
|
||||
exchCfg.CurrencyPairs.StorePairs(assetType, pairs, enabled)
|
||||
return nil
|
||||
}
|
||||
@@ -421,16 +396,12 @@ func (c *Config) GetCurrencyPairConfig(exchName string, assetType asset.Item) (*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
supports, err := c.SupportsExchangeAssetType(exchName, assetType)
|
||||
err = c.SupportsExchangeAssetType(exchName, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !supports {
|
||||
return nil, fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType)
|
||||
}
|
||||
|
||||
return exchCfg.CurrencyPairs.Get(assetType), nil
|
||||
return exchCfg.CurrencyPairs.Get(assetType)
|
||||
}
|
||||
|
||||
// CheckPairConfigFormats checks to see if the pair config format is valid
|
||||
@@ -508,60 +479,128 @@ func (c *Config) CheckPairConsistency(exchName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var atLeastOneEnabled bool
|
||||
for x := range assetTypes {
|
||||
enabledPairs, err := c.GetEnabledPairs(exchName, assetTypes[x])
|
||||
if err == nil {
|
||||
if len(enabledPairs) != 0 {
|
||||
atLeastOneEnabled = true
|
||||
continue
|
||||
}
|
||||
var enabled bool
|
||||
enabled, err = c.AssetTypeEnabled(assetTypes[x], exchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
var availPairs currency.Pairs
|
||||
availPairs, err = c.GetAvailablePairs(exchName, assetTypes[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.SetPairs(exchName,
|
||||
assetTypes[x],
|
||||
true,
|
||||
currency.Pairs{availPairs.GetRandomPair()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
atLeastOneEnabled = true
|
||||
continue
|
||||
}
|
||||
|
||||
// On error an enabled pair is not found in the available pairs list
|
||||
// so remove and report
|
||||
availPairs, err := c.GetAvailablePairs(exchName, assetTypes[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
availPairs, _ := c.GetAvailablePairs(exchName, assetTypes[x])
|
||||
if len(availPairs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var pairs, pairsRemoved currency.Pairs
|
||||
update := false
|
||||
|
||||
if len(enabledPairs) > 0 {
|
||||
for x := range enabledPairs {
|
||||
if !availPairs.Contains(enabledPairs[x], true) {
|
||||
update = true
|
||||
pairsRemoved = append(pairsRemoved, enabledPairs[x])
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, enabledPairs[x])
|
||||
for x := range enabledPairs {
|
||||
if !availPairs.Contains(enabledPairs[x], true) {
|
||||
pairsRemoved = append(pairsRemoved, enabledPairs[x])
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
update = true
|
||||
pairs = append(pairs, enabledPairs[x])
|
||||
}
|
||||
|
||||
if !update {
|
||||
if len(pairsRemoved) == 0 {
|
||||
return fmt.Errorf("check pair consistency fault for asset %s, conflict found but no pairs removed",
|
||||
assetTypes[x])
|
||||
}
|
||||
|
||||
// Flush corrupted/misspelled enabled pairs in config
|
||||
err = c.SetPairs(exchName, assetTypes[x], true, pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs list, as it isn't located in the available pairs list.\n",
|
||||
exchName,
|
||||
assetTypes[x],
|
||||
pairsRemoved.Strings())
|
||||
|
||||
if len(pairs) != 0 {
|
||||
atLeastOneEnabled = true
|
||||
continue
|
||||
}
|
||||
|
||||
if len(pairs) == 0 || len(enabledPairs) == 0 {
|
||||
newPair := availPairs.GetRandomPair()
|
||||
c.SetPairs(exchName, assetTypes[x], true, currency.Pairs{newPair})
|
||||
log.Warnf(log.ExchangeSys, "Exchange %s: [%v] No enabled pairs found in available pairs, randomly added %v pair.\n",
|
||||
exchName, assetTypes[x], newPair)
|
||||
continue
|
||||
} else {
|
||||
c.SetPairs(exchName, assetTypes[x], true, pairs)
|
||||
enabled, err := c.AssetTypeEnabled(assetTypes[x], exchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Warnf(log.ExchangeSys, "Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.\n",
|
||||
exchName, assetTypes[x], pairsRemoved.Strings())
|
||||
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.SetPairs(exchName,
|
||||
assetTypes[x],
|
||||
true,
|
||||
currency.Pairs{availPairs.GetRandomPair()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
atLeastOneEnabled = true
|
||||
}
|
||||
|
||||
// If no pair is enabled across the entire range of assets, then atleast
|
||||
// enable one and turn on the asset type
|
||||
if !atLeastOneEnabled {
|
||||
avail, err := c.GetAvailablePairs(exchName, assetTypes[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newPair := avail.GetRandomPair()
|
||||
err = c.SetPairs(exchName, assetTypes[0], true, currency.Pairs{newPair})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Exchange %s: [%v] No enabled pairs found in available pairs list, randomly added %v pair.\n",
|
||||
exchName,
|
||||
assetTypes[0],
|
||||
newPair)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportsPair returns true or not whether the exchange supports the supplied
|
||||
// pair
|
||||
func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType asset.Item) (bool, error) {
|
||||
func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType asset.Item) bool {
|
||||
pairs, err := c.GetAvailablePairs(exchName, assetType)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
return pairs.Contains(p, false), nil
|
||||
return pairs.Contains(p, false)
|
||||
}
|
||||
|
||||
// GetPairFormat returns the exchanges pair config storage format
|
||||
@@ -571,25 +610,31 @@ func (c *Config) GetPairFormat(exchName string, assetType asset.Item) (currency.
|
||||
return currency.PairFormat{}, err
|
||||
}
|
||||
|
||||
supports, err := c.SupportsExchangeAssetType(exchName, assetType)
|
||||
err = c.SupportsExchangeAssetType(exchName, assetType)
|
||||
if err != nil {
|
||||
return currency.PairFormat{}, err
|
||||
}
|
||||
|
||||
if !supports {
|
||||
return currency.PairFormat{},
|
||||
fmt.Errorf("exchange %s does not support asset type %s", exchName,
|
||||
assetType)
|
||||
}
|
||||
|
||||
if exchCfg.CurrencyPairs.UseGlobalFormat {
|
||||
return *exchCfg.CurrencyPairs.ConfigFormat, nil
|
||||
}
|
||||
|
||||
p := exchCfg.CurrencyPairs.Get(assetType)
|
||||
p, err := exchCfg.CurrencyPairs.Get(assetType)
|
||||
if err != nil {
|
||||
return currency.PairFormat{}, err
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return currency.PairFormat{},
|
||||
fmt.Errorf("exchange %s pair store for asset type %s is nil", exchName,
|
||||
fmt.Errorf("exchange %s pair store for asset type %s is nil",
|
||||
exchName,
|
||||
assetType)
|
||||
}
|
||||
|
||||
if p.ConfigFormat == nil {
|
||||
return currency.PairFormat{},
|
||||
fmt.Errorf("exchange %s pair config format for asset type %s is nil",
|
||||
exchName,
|
||||
assetType)
|
||||
}
|
||||
|
||||
@@ -608,7 +653,11 @@ func (c *Config) GetAvailablePairs(exchName string, assetType asset.Item) (curre
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pairs := exchCfg.CurrencyPairs.GetPairs(assetType, false)
|
||||
pairs, err := exchCfg.CurrencyPairs.GetPairs(assetType, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pairs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -618,7 +667,7 @@ func (c *Config) GetAvailablePairs(exchName string, assetType asset.Item) (curre
|
||||
}
|
||||
|
||||
// GetEnabledPairs returns a list of currency pairs for a specifc exchange
|
||||
func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) ([]currency.Pair, error) {
|
||||
func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) (currency.Pairs, error) {
|
||||
exchCfg, err := c.GetExchangeConfig(exchName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -629,13 +678,19 @@ func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) ([]curre
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pairs := exchCfg.CurrencyPairs.GetPairs(assetType, true)
|
||||
pairs, err := exchCfg.CurrencyPairs.GetPairs(assetType, true)
|
||||
if err != nil {
|
||||
return pairs, err
|
||||
}
|
||||
|
||||
if pairs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return pairs.Format(pairFormat.Delimiter, pairFormat.Index,
|
||||
pairFormat.Uppercase), nil
|
||||
return pairs.Format(pairFormat.Delimiter,
|
||||
pairFormat.Index,
|
||||
pairFormat.Uppercase),
|
||||
nil
|
||||
}
|
||||
|
||||
// GetEnabledExchanges returns a list of enabled exchanges
|
||||
@@ -843,16 +898,6 @@ func (c *Config) CheckExchangeConfigValues() error {
|
||||
c.Exchanges[i].CurrencyPairs.ConfigFormat = c.Exchanges[i].ConfigCurrencyPairFormat
|
||||
c.Exchanges[i].CurrencyPairs.RequestFormat = c.Exchanges[i].RequestCurrencyPairFormat
|
||||
|
||||
if c.Exchanges[i].AssetTypes == nil {
|
||||
c.Exchanges[i].CurrencyPairs.AssetTypes = asset.Items{
|
||||
asset.Spot,
|
||||
}
|
||||
} else {
|
||||
c.Exchanges[i].CurrencyPairs.AssetTypes = asset.New(
|
||||
strings.ToLower(*c.Exchanges[i].AssetTypes),
|
||||
)
|
||||
}
|
||||
|
||||
var availPairs, enabledPairs currency.Pairs
|
||||
if c.Exchanges[i].AvailablePairs != nil {
|
||||
availPairs = *c.Exchanges[i].AvailablePairs
|
||||
@@ -865,8 +910,9 @@ func (c *Config) CheckExchangeConfigValues() error {
|
||||
c.Exchanges[i].CurrencyPairs.UseGlobalFormat = true
|
||||
c.Exchanges[i].CurrencyPairs.Store(asset.Spot,
|
||||
currency.PairStore{
|
||||
Available: availPairs,
|
||||
Enabled: enabledPairs,
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Available: availPairs,
|
||||
Enabled: enabledPairs,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -877,6 +923,50 @@ func (c *Config) CheckExchangeConfigValues() error {
|
||||
c.Exchanges[i].AssetTypes = nil
|
||||
c.Exchanges[i].AvailablePairs = nil
|
||||
c.Exchanges[i].EnabledPairs = nil
|
||||
} else {
|
||||
assets := c.Exchanges[i].CurrencyPairs.GetAssetTypes()
|
||||
var atLeastOne bool
|
||||
for index := range assets {
|
||||
err := c.Exchanges[i].CurrencyPairs.IsAssetEnabled(assets[index])
|
||||
if err != nil {
|
||||
// Checks if we have an old config without the ability to
|
||||
// enable disable the entire asset
|
||||
if err.Error() == "cannot ascertain if asset is enabled, variable is nil" {
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Exchange %s: upgrading config for asset type %s and setting enabled.\n",
|
||||
c.Exchanges[i].Name,
|
||||
assets[index])
|
||||
err = c.Exchanges[i].CurrencyPairs.SetAssetEnabled(assets[index], true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
atLeastOne = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
atLeastOne = true
|
||||
}
|
||||
|
||||
if !atLeastOne {
|
||||
if len(assets) == 0 {
|
||||
c.Exchanges[i].Enabled = false
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"%s no assets found, disabling...",
|
||||
c.Exchanges[i].Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// turn on an asset if all disabled
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"%s assets disabled, turning on asset %s",
|
||||
c.Exchanges[i].Name,
|
||||
assets[0])
|
||||
|
||||
err := c.Exchanges[i].CurrencyPairs.SetAssetEnabled(assets[0], true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Exchanges[i].Enabled {
|
||||
@@ -885,71 +975,101 @@ func (c *Config) CheckExchangeConfigValues() error {
|
||||
c.Exchanges[i].Enabled = false
|
||||
continue
|
||||
}
|
||||
if (c.Exchanges[i].API.AuthenticatedSupport || c.Exchanges[i].API.AuthenticatedWebsocketSupport) && c.Exchanges[i].API.CredentialsValidator != nil {
|
||||
if (c.Exchanges[i].API.AuthenticatedSupport || c.Exchanges[i].API.AuthenticatedWebsocketSupport) &&
|
||||
c.Exchanges[i].API.CredentialsValidator != nil {
|
||||
var failed bool
|
||||
if c.Exchanges[i].API.CredentialsValidator.RequiresKey && (c.Exchanges[i].API.Credentials.Key == "" || c.Exchanges[i].API.Credentials.Key == DefaultAPIKey) {
|
||||
if c.Exchanges[i].API.CredentialsValidator.RequiresKey &&
|
||||
(c.Exchanges[i].API.Credentials.Key == "" || c.Exchanges[i].API.Credentials.Key == DefaultAPIKey) {
|
||||
failed = true
|
||||
}
|
||||
|
||||
if c.Exchanges[i].API.CredentialsValidator.RequiresSecret && (c.Exchanges[i].API.Credentials.Secret == "" || c.Exchanges[i].API.Credentials.Secret == DefaultAPISecret) {
|
||||
if c.Exchanges[i].API.CredentialsValidator.RequiresSecret &&
|
||||
(c.Exchanges[i].API.Credentials.Secret == "" || c.Exchanges[i].API.Credentials.Secret == DefaultAPISecret) {
|
||||
failed = true
|
||||
}
|
||||
|
||||
if c.Exchanges[i].API.CredentialsValidator.RequiresClientID && (c.Exchanges[i].API.Credentials.ClientID == DefaultAPIClientID || c.Exchanges[i].API.Credentials.ClientID == "") {
|
||||
if c.Exchanges[i].API.CredentialsValidator.RequiresClientID &&
|
||||
(c.Exchanges[i].API.Credentials.ClientID == DefaultAPIClientID || c.Exchanges[i].API.Credentials.ClientID == "") {
|
||||
failed = true
|
||||
}
|
||||
|
||||
if failed {
|
||||
c.Exchanges[i].API.AuthenticatedSupport = false
|
||||
c.Exchanges[i].API.AuthenticatedWebsocketSupport = false
|
||||
log.Warnf(log.ExchangeSys, WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name)
|
||||
log.Warnf(log.ConfigMgr, WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name)
|
||||
}
|
||||
}
|
||||
if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates && !c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates {
|
||||
if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates &&
|
||||
!c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates {
|
||||
lastUpdated := convert.UnixTimestampToTime(c.Exchanges[i].CurrencyPairs.LastUpdated)
|
||||
lastUpdated = lastUpdated.AddDate(0, 0, pairsLastUpdatedWarningThreshold)
|
||||
if lastUpdated.Unix() <= time.Now().Unix() {
|
||||
log.Warnf(log.ExchangeSys, WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, pairsLastUpdatedWarningThreshold)
|
||||
log.Warnf(log.ConfigMgr,
|
||||
WarningPairsLastUpdatedThresholdExceeded,
|
||||
c.Exchanges[i].Name,
|
||||
pairsLastUpdatedWarningThreshold)
|
||||
}
|
||||
}
|
||||
if c.Exchanges[i].HTTPTimeout <= 0 {
|
||||
log.Warnf(log.ExchangeSys, "Exchange %s HTTP Timeout value not set, defaulting to %v.\n", c.Exchanges[i].Name, defaultHTTPTimeout)
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Exchange %s HTTP Timeout value not set, defaulting to %v.\n",
|
||||
c.Exchanges[i].Name,
|
||||
defaultHTTPTimeout)
|
||||
c.Exchanges[i].HTTPTimeout = defaultHTTPTimeout
|
||||
}
|
||||
|
||||
if c.Exchanges[i].WebsocketResponseCheckTimeout <= 0 {
|
||||
log.Warnf(log.ExchangeSys, "Exchange %s Websocket response check timeout value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name, defaultWebsocketResponseCheckTimeout)
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Exchange %s Websocket response check timeout value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name,
|
||||
defaultWebsocketResponseCheckTimeout)
|
||||
c.Exchanges[i].WebsocketResponseCheckTimeout = defaultWebsocketResponseCheckTimeout
|
||||
}
|
||||
|
||||
if c.Exchanges[i].WebsocketResponseMaxLimit <= 0 {
|
||||
log.Warnf(log.ExchangeSys, "Exchange %s Websocket response max limit value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name, defaultWebsocketResponseMaxLimit)
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Exchange %s Websocket response max limit value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name,
|
||||
defaultWebsocketResponseMaxLimit)
|
||||
c.Exchanges[i].WebsocketResponseMaxLimit = defaultWebsocketResponseMaxLimit
|
||||
}
|
||||
if c.Exchanges[i].WebsocketTrafficTimeout <= 0 {
|
||||
log.Warnf(log.ExchangeSys, "Exchange %s Websocket response traffic timeout value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name, defaultWebsocketTrafficTimeout)
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Exchange %s Websocket response traffic timeout value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name,
|
||||
defaultWebsocketTrafficTimeout)
|
||||
c.Exchanges[i].WebsocketTrafficTimeout = defaultWebsocketTrafficTimeout
|
||||
}
|
||||
if c.Exchanges[i].WebsocketOrderbookBufferLimit <= 0 {
|
||||
log.Warnf(log.ExchangeSys, "Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name, defaultWebsocketOrderbookBufferLimit)
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name,
|
||||
defaultWebsocketOrderbookBufferLimit)
|
||||
c.Exchanges[i].WebsocketOrderbookBufferLimit = defaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
err := c.CheckPairConsistency(c.Exchanges[i].Name)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "Exchange %s: CheckPairConsistency error: %s\n", c.Exchanges[i].Name, err)
|
||||
log.Errorf(log.ConfigMgr,
|
||||
"Exchange %s: CheckPairConsistency error: %s\n",
|
||||
c.Exchanges[i].Name,
|
||||
err)
|
||||
c.Exchanges[i].Enabled = false
|
||||
continue
|
||||
}
|
||||
|
||||
c.CheckExchangeAssetsConsistency(c.Exchanges[i].Name)
|
||||
|
||||
for x := range c.Exchanges[i].BankAccounts {
|
||||
if !c.Exchanges[i].BankAccounts[x].Enabled {
|
||||
continue
|
||||
}
|
||||
err := c.Exchanges[i].BankAccounts[x].Validate()
|
||||
if err != nil {
|
||||
c.Exchanges[i].BankAccounts[x].Enabled = false
|
||||
log.Warnln(log.ConfigMgr, err.Error())
|
||||
}
|
||||
}
|
||||
exchanges++
|
||||
}
|
||||
}
|
||||
|
||||
if exchanges == 0 {
|
||||
return errors.New(ErrNoEnabledExchanges)
|
||||
}
|
||||
@@ -1104,8 +1224,8 @@ func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool, assetType asset.I
|
||||
continue
|
||||
}
|
||||
|
||||
supports, _ := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType)
|
||||
if !supports {
|
||||
err := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1118,13 +1238,12 @@ func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool, assetType asset.I
|
||||
}
|
||||
|
||||
for x := range c.Exchanges {
|
||||
supports, _ := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType)
|
||||
if !supports {
|
||||
err := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var pairs []currency.Pair
|
||||
var err error
|
||||
if !c.Exchanges[x].Enabled && enabledOnly {
|
||||
pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, assetType)
|
||||
} else {
|
||||
@@ -1475,7 +1594,7 @@ func (c *Config) ReadConfig(configPath string, dryrun bool) error {
|
||||
}
|
||||
|
||||
if !ConfirmECS(fileData) {
|
||||
err = ConfirmConfigJSON(fileData, &c)
|
||||
err = json.Unmarshal(fileData, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1493,38 +1612,39 @@ func (c *Config) ReadConfig(configPath string, dryrun bool) error {
|
||||
return c.SaveConfig(defaultPath, dryrun)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errCounter := 0
|
||||
for {
|
||||
if errCounter >= maxAuthFailures {
|
||||
return errors.New("failed to decrypt config after 3 attempts")
|
||||
}
|
||||
key, err := PromptForConfigKey(IsInitialSetup)
|
||||
if err != nil {
|
||||
log.Errorf(log.ConfigMgr, "PromptForConfigKey err: %s", err)
|
||||
errCounter++
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var f []byte
|
||||
f = append(f, fileData...)
|
||||
data, err := DecryptConfigFile(f, key)
|
||||
if err != nil {
|
||||
log.Errorf(log.ConfigMgr, "DecryptConfigFile err: %s", err)
|
||||
errCounter++
|
||||
continue
|
||||
}
|
||||
|
||||
err = ConfirmConfigJSON(data, &c)
|
||||
if err != nil {
|
||||
if errCounter < maxAuthFailures {
|
||||
log.Error(log.ConfigMgr, "Invalid password.")
|
||||
}
|
||||
errCounter++
|
||||
continue
|
||||
}
|
||||
break
|
||||
errCounter := 0
|
||||
for {
|
||||
if errCounter >= maxAuthFailures {
|
||||
return errors.New("failed to decrypt config after 3 attempts")
|
||||
}
|
||||
key, err := PromptForConfigKey(IsInitialSetup)
|
||||
if err != nil {
|
||||
log.Errorf(log.ConfigMgr, "PromptForConfigKey err: %s", err)
|
||||
errCounter++
|
||||
continue
|
||||
}
|
||||
|
||||
var f []byte
|
||||
f = append(f, fileData...)
|
||||
data, err := DecryptConfigFile(f, key)
|
||||
if err != nil {
|
||||
log.Errorf(log.ConfigMgr, "DecryptConfigFile err: %s", err)
|
||||
errCounter++
|
||||
continue
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, c)
|
||||
if err != nil {
|
||||
if errCounter < maxAuthFailures {
|
||||
log.Error(log.ConfigMgr, "Invalid password.")
|
||||
}
|
||||
errCounter++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1611,12 +1731,16 @@ func (c *Config) CheckRemoteControlConfig() {
|
||||
func (c *Config) CheckConfig() error {
|
||||
err := c.CheckLoggerConfig()
|
||||
if err != nil {
|
||||
log.Errorf(log.ConfigMgr, "Failed to configure logger, some logging features unavailable: %s\n", err)
|
||||
log.Errorf(log.ConfigMgr,
|
||||
"Failed to configure logger, some logging features unavailable: %s\n",
|
||||
err)
|
||||
}
|
||||
|
||||
err = c.checkDatabaseConfig()
|
||||
if err != nil {
|
||||
log.Errorf(log.DatabaseMgr, "Failed to configure database: %v", err)
|
||||
log.Errorf(log.DatabaseMgr,
|
||||
"Failed to configure database: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
err = c.CheckExchangeConfigValues()
|
||||
@@ -1626,7 +1750,9 @@ func (c *Config) CheckConfig() error {
|
||||
|
||||
err = c.checkGCTScriptConfig()
|
||||
if err != nil {
|
||||
log.Errorf(log.Global, "Failed to configure gctscript, feature has been disabled: %s\n", err)
|
||||
log.Errorf(log.Global,
|
||||
"Failed to configure gctscript, feature has been disabled: %s\n",
|
||||
err)
|
||||
}
|
||||
|
||||
c.CheckConnectionMonitorConfig()
|
||||
@@ -1641,7 +1767,9 @@ func (c *Config) CheckConfig() error {
|
||||
}
|
||||
|
||||
if c.GlobalHTTPTimeout <= 0 {
|
||||
log.Warnf(log.ConfigMgr, "Global HTTP Timeout value not set, defaulting to %v.\n", defaultHTTPTimeout)
|
||||
log.Warnf(log.ConfigMgr,
|
||||
"Global HTTP Timeout value not set, defaulting to %v.\n",
|
||||
defaultHTTPTimeout)
|
||||
c.GlobalHTTPTimeout = defaultHTTPTimeout
|
||||
}
|
||||
|
||||
@@ -1703,3 +1831,17 @@ func (c *Config) RemoveExchange(exchName string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AssetTypeEnabled checks to see if the asset type is enabled in configuration
|
||||
func (c *Config) AssetTypeEnabled(a asset.Item, exch string) (bool, error) {
|
||||
cfg, err := c.GetExchangeConfig(exch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = cfg.CurrencyPairs.IsAssetEnabled(a)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -167,11 +166,6 @@ func DecryptConfigFile(configData, key []byte) ([]byte, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ConfirmConfigJSON confirms JSON in file
|
||||
func ConfirmConfigJSON(file []byte, result interface{}) error {
|
||||
return json.Unmarshal(file, &result)
|
||||
}
|
||||
|
||||
// ConfirmSalt checks whether the encrypted data contains a salt
|
||||
func ConfirmSalt(file []byte) bool {
|
||||
return bytes.Contains(file, []byte(SaltPrefix))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -84,19 +83,6 @@ func TestDecryptConfigFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmConfigJSON(t *testing.T) {
|
||||
var result interface{}
|
||||
testConfirmJSON, err := ioutil.ReadFile(TestFile)
|
||||
if err != nil {
|
||||
t.Errorf("testConfirmJSON: %s", err)
|
||||
}
|
||||
|
||||
err = ConfirmConfigJSON(testConfirmJSON, &result)
|
||||
if err != nil || result == nil {
|
||||
t.Errorf("testConfirmJSON: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmECS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/connchecker"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
@@ -21,6 +22,7 @@ const (
|
||||
defaultEnabledExchanges = 28
|
||||
testFakeExchangeName = "Stampbit"
|
||||
testPair = "BTC-USD"
|
||||
testString = "test"
|
||||
)
|
||||
|
||||
func TestGetCurrencyConfig(t *testing.T) {
|
||||
@@ -32,6 +34,22 @@ func TestGetCurrencyConfig(t *testing.T) {
|
||||
_ = cfg.GetCurrencyConfig()
|
||||
}
|
||||
|
||||
func TestGetClientBankAccounts(t *testing.T) {
|
||||
cfg := GetConfig()
|
||||
err := cfg.LoadConfig(TestFile, true)
|
||||
if err != nil {
|
||||
t.Fatal("GetExchangeBankAccounts LoadConfig error", err)
|
||||
}
|
||||
_, err = cfg.GetClientBankAccounts("Kraken", "USD")
|
||||
if err != nil {
|
||||
t.Error("GetExchangeBankAccounts error", err)
|
||||
}
|
||||
_, err = cfg.GetClientBankAccounts("noob exchange", "USD")
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExchangeBankAccounts(t *testing.T) {
|
||||
cfg := GetConfig()
|
||||
err := cfg.LoadConfig(TestFile, true)
|
||||
@@ -48,6 +66,17 @@ func TestGetExchangeBankAccounts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckBankAccountConfig(t *testing.T) {
|
||||
cfg := GetConfig()
|
||||
err := cfg.LoadConfig(TestFile, true)
|
||||
if err != nil {
|
||||
t.Error("GetExchangeBankAccounts LoadConfig error", err)
|
||||
}
|
||||
|
||||
cfg.BankAccounts[0].Enabled = true
|
||||
cfg.CheckBankAccountConfig()
|
||||
}
|
||||
|
||||
func TestUpdateExchangeBankAccounts(t *testing.T) {
|
||||
cfg := GetConfig()
|
||||
err := cfg.LoadConfig(TestFile, true)
|
||||
@@ -84,7 +113,7 @@ func TestUpdateClientBankAccounts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error("UpdateClientBankAccounts LoadConfig error", err)
|
||||
}
|
||||
b := banking.Account{Enabled: false, BankName: "test", AccountNumber: "0234"}
|
||||
b := banking.Account{Enabled: false, BankName: testString, AccountNumber: "0234"}
|
||||
err = cfg.UpdateClientBankAccounts(&b)
|
||||
if err != nil {
|
||||
t.Error("UpdateClientBankAccounts error", err)
|
||||
@@ -185,7 +214,7 @@ func TestPurgeExchangeCredentials(t *testing.T) {
|
||||
var c Config
|
||||
c.Exchanges = []ExchangeConfig{
|
||||
{
|
||||
Name: "test",
|
||||
Name: testString,
|
||||
API: APIConfig{
|
||||
AuthenticatedSupport: true,
|
||||
AuthenticatedWebsocketSupport: true,
|
||||
@@ -219,7 +248,7 @@ func TestPurgeExchangeCredentials(t *testing.T) {
|
||||
|
||||
c.PurgeExchangeAPICredentials()
|
||||
|
||||
exchCfg, err := c.GetExchangeConfig("test")
|
||||
exchCfg, err := c.GetExchangeConfig(testString)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -257,8 +286,8 @@ func TestUpdateCommunicationsConfig(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error("UpdateCommunicationsConfig LoadConfig error", err)
|
||||
}
|
||||
cfg.UpdateCommunicationsConfig(&CommunicationsConfig{SlackConfig: SlackConfig{Name: "TEST"}})
|
||||
if cfg.Communications.SlackConfig.Name != "TEST" {
|
||||
cfg.UpdateCommunicationsConfig(&CommunicationsConfig{SlackConfig: SlackConfig{Name: testString}})
|
||||
if cfg.Communications.SlackConfig.Name != testString {
|
||||
t.Error("UpdateCommunicationsConfig LoadConfig error")
|
||||
}
|
||||
}
|
||||
@@ -308,7 +337,7 @@ func TestCheckCommunicationsConfig(t *testing.T) {
|
||||
cfg.SMS = &SMSGlobalConfig{}
|
||||
cfg.Communications.SMSGlobalConfig.Name = ""
|
||||
cfg.CheckCommunicationsConfig()
|
||||
if cfg.Communications.SMSGlobalConfig.Password != "test" {
|
||||
if cfg.Communications.SMSGlobalConfig.Password != testString {
|
||||
t.Error("CheckCommunicationsConfig error:", err)
|
||||
}
|
||||
|
||||
@@ -389,9 +418,9 @@ func TestGetExchangeAssetTypes(t *testing.T) {
|
||||
ExchangeConfig{
|
||||
Name: testFakeExchangeName,
|
||||
CurrencyPairs: ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
asset.Futures,
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: new(currency.PairStore),
|
||||
asset.Futures: new(currency.PairStore),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -403,7 +432,7 @@ func TestGetExchangeAssetTypes(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if assets.JoinToString(",") != "spot,futures" {
|
||||
if !assets.Contains(asset.Spot) || !assets.Contains(asset.Futures) {
|
||||
t.Error("unexpected results")
|
||||
}
|
||||
|
||||
@@ -417,7 +446,7 @@ func TestGetExchangeAssetTypes(t *testing.T) {
|
||||
func TestSupportsExchangeAssetType(t *testing.T) {
|
||||
t.Parallel()
|
||||
var c Config
|
||||
_, err := c.SupportsExchangeAssetType("void", asset.Spot)
|
||||
err := c.SupportsExchangeAssetType("void", asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("Expected error for non-existent exchange")
|
||||
}
|
||||
@@ -426,73 +455,30 @@ func TestSupportsExchangeAssetType(t *testing.T) {
|
||||
ExchangeConfig{
|
||||
Name: testFakeExchangeName,
|
||||
CurrencyPairs: ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
asset.Futures,
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: new(currency.PairStore),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
supports, err := c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot)
|
||||
err = c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !supports {
|
||||
t.Error("exchange should support spot asset item")
|
||||
}
|
||||
|
||||
_, err = c.SupportsExchangeAssetType(testFakeExchangeName, "asdf")
|
||||
err = c.SupportsExchangeAssetType(testFakeExchangeName, "asdf")
|
||||
if err == nil {
|
||||
t.Error("Expected error from invalid asset item")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs = nil
|
||||
_, err = c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot)
|
||||
err = c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("Expected error from nil pair manager")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckExchangeAssetsConsistency(t *testing.T) {
|
||||
t.Parallel()
|
||||
var c Config
|
||||
// Test for non-existent exchange
|
||||
c.CheckExchangeAssetsConsistency("void")
|
||||
|
||||
c.Exchanges = append(c.Exchanges,
|
||||
ExchangeConfig{
|
||||
Name: testFakeExchangeName,
|
||||
},
|
||||
)
|
||||
|
||||
// Tests for nil currency pairs store but valid exchange name
|
||||
c.CheckExchangeAssetsConsistency(testFakeExchangeName)
|
||||
|
||||
// Simulate testing a diff between stored asset types (config loading)
|
||||
// and pair store
|
||||
c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
asset.Futures,
|
||||
asset.Index,
|
||||
},
|
||||
}
|
||||
c.Exchanges[0].CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.PerpetualContract] = ¤cy.PairStore{}
|
||||
c.CheckExchangeAssetsConsistency(testFakeExchangeName)
|
||||
|
||||
supports, err := c.SupportsExchangeAssetType(testFakeExchangeName, asset.PerpetualContract)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if supports {
|
||||
t.Error("perpetual contract should have been removed from the pair manager")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPairs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -524,9 +510,8 @@ func TestSetPairs(t *testing.T) {
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
asset.Futures,
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: new(currency.PairStore),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -562,10 +547,6 @@ func TestGetCurrencyPairConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
pm := ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
asset.Futures,
|
||||
},
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
@@ -610,11 +591,6 @@ func TestCheckPairConfigFormats(t *testing.T) {
|
||||
c.Exchanges = append(c.Exchanges,
|
||||
ExchangeConfig{
|
||||
Name: testFakeExchangeName,
|
||||
CurrencyPairs: ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Item("wrong"),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -622,23 +598,40 @@ func TestCheckPairConfigFormats(t *testing.T) {
|
||||
t.Error("nil pair store should return an error")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs.AssetTypes = asset.Items{asset.Spot}
|
||||
c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
RequestFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {},
|
||||
asset.Futures: {},
|
||||
},
|
||||
asset.Futures: {
|
||||
RequestFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
}
|
||||
if err := c.CheckPairConfigFormats(testFakeExchangeName); err == nil {
|
||||
t.Error("error cannot be nil")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
RequestFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
},
|
||||
asset.Futures: {
|
||||
RequestFormat: ¤cy.PairFormat{},
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := c.CheckPairConfigFormats(testFakeExchangeName); err != nil {
|
||||
t.Error("nil pairs should be okay to continue")
|
||||
}
|
||||
|
||||
avail, err := currency.NewPairDelimiter(testPair, "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enabled, err := currency.NewPairDelimiter("BTC~USD", "~")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test having a pair index and delimiter set at the same time throws an error
|
||||
c.Exchanges[0].CurrencyPairs.AssetTypes = asset.Items{asset.Spot}
|
||||
c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
@@ -651,10 +644,10 @@ func TestCheckPairConfigFormats(t *testing.T) {
|
||||
Index: "USD",
|
||||
},
|
||||
Available: currency.Pairs{
|
||||
currency.NewPairDelimiter(testPair, "-"),
|
||||
avail,
|
||||
},
|
||||
Enabled: currency.Pairs{
|
||||
currency.NewPairDelimiter("BTC~USD", "~"),
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -698,11 +691,6 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
c.Exchanges = append(c.Exchanges,
|
||||
ExchangeConfig{
|
||||
Name: testFakeExchangeName,
|
||||
CurrencyPairs: ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -711,33 +699,71 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
t.Error("nil pair store should return an error")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: false,
|
||||
Delimiter: "_",
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "_",
|
||||
},
|
||||
Enabled: currency.Pairs{
|
||||
currency.NewPairDelimiter("BTC_USD", "_"),
|
||||
enabled, err := currency.NewPairDelimiter("BTC_USD", "_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: {
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: false,
|
||||
Delimiter: "_",
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "_",
|
||||
},
|
||||
Enabled: currency.Pairs{
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test for nil avail pairs
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("nil available pairs should continue")
|
||||
err = c.CheckPairConsistency(testFakeExchangeName)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
p1, err := currency.NewPairDelimiter("LTC_USD", "_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test that enabled pair is not found in the available pairs
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{
|
||||
currency.NewPairDelimiter("LTC_USD", "_"),
|
||||
p1,
|
||||
}
|
||||
|
||||
// LTC_USD is only found in the available pairs list and should therefor
|
||||
// be added to the enabled pairs list due to the atLestOneEnabled code
|
||||
err = c.CheckPairConsistency(testFakeExchangeName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, item := range c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled {
|
||||
if !item.Equal(p1) {
|
||||
t.Fatal("LTC_USD should be contained in the enabled pairs list")
|
||||
}
|
||||
}
|
||||
|
||||
p2, err := currency.NewPairDelimiter("BTC_USD", "_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add the BTC_USD pair and see result
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{
|
||||
p1,
|
||||
p2,
|
||||
}
|
||||
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test that an empty enabled pair is populated with an available pair
|
||||
@@ -746,10 +772,14 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
if len(c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled) != 1 {
|
||||
t.Fatal("should be populated with atleast one currency pair")
|
||||
}
|
||||
|
||||
// Test that an invalid enabled pair is removed from the list
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{
|
||||
currency.NewPairDelimiter("LTC_USD", "_"),
|
||||
currency.NewPairDelimiter("BTC_USD", "_"),
|
||||
p1,
|
||||
p2,
|
||||
}
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
@@ -760,6 +790,48 @@ func TestCheckPairConsistency(t *testing.T) {
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = convert.BoolPtr(true)
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{}
|
||||
|
||||
// Test no conflict and atleast one on enabled asset type
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = convert.BoolPtr(true)
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{currency.NewPair(currency.DASH, currency.USD)}
|
||||
|
||||
// Test with conflict and atleast one on enabled asset type
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = convert.BoolPtr(false)
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{}
|
||||
|
||||
// Test no conflict and atleast one on disabled asset type
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{
|
||||
currency.NewPair(currency.DASH, currency.USD),
|
||||
p1,
|
||||
p2,
|
||||
}
|
||||
|
||||
// Test with conflict and atleast one on disabled asset type
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = nil
|
||||
|
||||
// assetType enabled failure check
|
||||
if err := c.CheckPairConsistency(testFakeExchangeName); err != nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportsPair(t *testing.T) {
|
||||
@@ -772,17 +844,15 @@ func TestSupportsPair(t *testing.T) {
|
||||
}
|
||||
|
||||
assetType := asset.Spot
|
||||
_, err = cfg.SupportsPair("asdf",
|
||||
currency.NewPair(currency.BTC, currency.USD), assetType)
|
||||
if err == nil {
|
||||
if cfg.SupportsPair("asdf",
|
||||
currency.NewPair(currency.BTC, currency.USD), assetType) {
|
||||
t.Error(
|
||||
"TestSupportsPair. Expected error from Non-existent exchange",
|
||||
)
|
||||
}
|
||||
|
||||
_, err = cfg.SupportsPair("Bitfinex",
|
||||
currency.NewPair(currency.BTC, currency.USD), assetType)
|
||||
if err != nil {
|
||||
if !cfg.SupportsPair("Bitfinex",
|
||||
currency.NewPair(currency.BTC, currency.USD), assetType) {
|
||||
t.Errorf(
|
||||
"TestSupportsPair. Incorrect values. Err: %s", err,
|
||||
)
|
||||
@@ -809,7 +879,26 @@ func TestGetPairFormat(t *testing.T) {
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{asset.Spot},
|
||||
UseGlobalFormat: false,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: false,
|
||||
Delimiter: "_",
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "_",
|
||||
},
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: nil,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = c.GetPairFormat(testFakeExchangeName, asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("Expected error from nil pair manager")
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: false,
|
||||
@@ -819,6 +908,9 @@ func TestGetPairFormat(t *testing.T) {
|
||||
Uppercase: true,
|
||||
Delimiter: "_",
|
||||
},
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: new(currency.PairStore),
|
||||
},
|
||||
}
|
||||
_, err = c.GetPairFormat(testFakeExchangeName, asset.Item("invalid"))
|
||||
if err == nil {
|
||||
@@ -876,12 +968,8 @@ func TestGetAvailablePairs(t *testing.T) {
|
||||
|
||||
c.Exchanges = append(c.Exchanges,
|
||||
ExchangeConfig{
|
||||
Name: testFakeExchangeName,
|
||||
CurrencyPairs: ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
},
|
||||
Name: testFakeExchangeName,
|
||||
CurrencyPairs: ¤cy.PairsManager{},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -923,12 +1011,8 @@ func TestGetEnabledPairs(t *testing.T) {
|
||||
|
||||
c.Exchanges = append(c.Exchanges,
|
||||
ExchangeConfig{
|
||||
Name: testFakeExchangeName,
|
||||
CurrencyPairs: ¤cy.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
},
|
||||
Name: testFakeExchangeName,
|
||||
CurrencyPairs: ¤cy.PairsManager{},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -953,6 +1037,11 @@ func TestGetEnabledPairs(t *testing.T) {
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{
|
||||
currency.NewPair(currency.BTC, currency.USD),
|
||||
}
|
||||
|
||||
c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{
|
||||
currency.NewPair(currency.BTC, currency.USD),
|
||||
}
|
||||
|
||||
_, err = c.GetEnabledPairs(testFakeExchangeName, asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -1183,16 +1272,15 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
|
||||
// Test API settings migration
|
||||
sptr := func(s string) *string { return &s }
|
||||
bptr := func(b bool) *bool { return &b }
|
||||
int64ptr := func(i int64) *int64 { return &i }
|
||||
|
||||
cfg.Exchanges[0].APIKey = sptr("awesomeKey")
|
||||
cfg.Exchanges[0].APISecret = sptr("meowSecret")
|
||||
cfg.Exchanges[0].ClientID = sptr("clientIDerino")
|
||||
cfg.Exchanges[0].APIAuthPEMKey = sptr("-----BEGIN EC PRIVATE KEY-----\nASDF\n-----END EC PRIVATE KEY-----\n")
|
||||
cfg.Exchanges[0].APIAuthPEMKeySupport = bptr(true)
|
||||
cfg.Exchanges[0].AuthenticatedAPISupport = bptr(true)
|
||||
cfg.Exchanges[0].AuthenticatedWebsocketAPISupport = bptr(true)
|
||||
cfg.Exchanges[0].APIAuthPEMKeySupport = convert.BoolPtr(true)
|
||||
cfg.Exchanges[0].AuthenticatedAPISupport = convert.BoolPtr(true)
|
||||
cfg.Exchanges[0].AuthenticatedWebsocketAPISupport = convert.BoolPtr(true)
|
||||
cfg.Exchanges[0].WebsocketURL = sptr("wss://1337")
|
||||
cfg.Exchanges[0].APIURL = sptr(APIURLNonDefaultMessage)
|
||||
cfg.Exchanges[0].APIURLSecondary = sptr(APIURLNonDefaultMessage)
|
||||
@@ -1230,8 +1318,8 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
|
||||
// Test feature and endpoint migrations migrations
|
||||
cfg.Exchanges[0].Features = nil
|
||||
cfg.Exchanges[0].SupportsAutoPairUpdates = bptr(true)
|
||||
cfg.Exchanges[0].Websocket = bptr(true)
|
||||
cfg.Exchanges[0].SupportsAutoPairUpdates = convert.BoolPtr(true)
|
||||
cfg.Exchanges[0].Websocket = convert.BoolPtr(true)
|
||||
cfg.Exchanges[0].API.Endpoints.URL = ""
|
||||
cfg.Exchanges[0].API.Endpoints.URLSecondary = ""
|
||||
cfg.Exchanges[0].API.Endpoints.WebsocketURL = ""
|
||||
@@ -1253,11 +1341,16 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
t.Error("unexpected values")
|
||||
}
|
||||
|
||||
p1, err := currency.NewPairDelimiter(testPair, "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test currency pair migration
|
||||
setupPairs := func(emptyAssets bool) {
|
||||
cfg.Exchanges[0].CurrencyPairs = nil
|
||||
p := currency.Pairs{
|
||||
currency.NewPairDelimiter(testPair, "-"),
|
||||
p1,
|
||||
}
|
||||
cfg.Exchanges[0].PairsLastUpdated = int64ptr(1234567)
|
||||
|
||||
@@ -1305,17 +1398,25 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
t.Error("unexpected request format values")
|
||||
}
|
||||
|
||||
if cfg.Exchanges[0].CurrencyPairs.AssetTypes.JoinToString(",") != "spot" ||
|
||||
if !cfg.Exchanges[0].CurrencyPairs.GetAssetTypes().Contains(asset.Spot) ||
|
||||
!cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat {
|
||||
t.Error("unexpected results")
|
||||
}
|
||||
|
||||
pairs := cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, true)
|
||||
pairs, err := cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(pairs) == 0 || pairs.Join() != testPair {
|
||||
t.Error("pairs not set properly")
|
||||
}
|
||||
|
||||
pairs = cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, false)
|
||||
pairs, err = cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(pairs) == 0 || pairs.Join() != testPair {
|
||||
t.Error("pairs not set properly")
|
||||
}
|
||||
@@ -1334,24 +1435,10 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
cfg.Exchanges[0].Features.Supports.RESTCapabilities.AutoPairUpdates = false
|
||||
cfg.Exchanges[0].Features.Supports.WebsocketCapabilities.AutoPairUpdates = false
|
||||
cfg.Exchanges[0].CurrencyPairs.LastUpdated = 0
|
||||
cfg.CheckExchangeConfigValues()
|
||||
|
||||
// Test exchange pair consistency error
|
||||
cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat = false
|
||||
backup := cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot]
|
||||
cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] = nil
|
||||
err = cfg.CheckExchangeConfigValues()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if cfg.Exchanges[0].Enabled {
|
||||
t.Error("exchange should have been disabled")
|
||||
}
|
||||
|
||||
// Restore to previous state
|
||||
cfg.Exchanges[0].Enabled = true
|
||||
cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat = true
|
||||
cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] = backup
|
||||
|
||||
// Test websocket and HTTP timeout values
|
||||
cfg.Exchanges[0].WebsocketResponseMaxLimit = 0
|
||||
@@ -1419,6 +1506,10 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
!cfg.Exchanges[0].API.AuthenticatedWebsocketSupport {
|
||||
t.Error("Expected AuthenticatedAPISupport and AuthenticatedWebsocketAPISupport to be false from invalid API keys")
|
||||
}
|
||||
|
||||
// Make a sneaky copy for bank account testing
|
||||
cpy := append(cfg.Exchanges[:0:0], cfg.Exchanges...)
|
||||
|
||||
// Test empty exchange name for an enabled exchange
|
||||
cfg.Exchanges[0].Enabled = true
|
||||
cfg.Exchanges[0].Name = ""
|
||||
@@ -1436,6 +1527,82 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("Expected error from no enabled exchanges")
|
||||
}
|
||||
|
||||
cfg.Exchanges = cpy
|
||||
// Check bank account validation for exchange
|
||||
cfg.Exchanges[0].BankAccounts = []banking.Account{
|
||||
{
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
err = cfg.CheckExchangeConfigValues()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if cfg.Exchanges[0].BankAccounts[0].Enabled {
|
||||
t.Fatal("bank aaccount details not provided this should disable")
|
||||
}
|
||||
|
||||
// Test international bank
|
||||
cfg.Exchanges[0].BankAccounts[0].Enabled = true
|
||||
cfg.Exchanges[0].BankAccounts[0].BankName = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].BankAddress = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].BankPostalCode = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].BankPostalCity = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].BankCountry = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].AccountName = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].SupportedCurrencies = "monopoly moneys"
|
||||
cfg.Exchanges[0].BankAccounts[0].IBAN = "some iban"
|
||||
cfg.Exchanges[0].BankAccounts[0].SWIFTCode = "some swifty"
|
||||
|
||||
err = cfg.CheckExchangeConfigValues()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !cfg.Exchanges[0].BankAccounts[0].Enabled {
|
||||
t.Fatal("bank aaccount details provided this should not disable")
|
||||
}
|
||||
|
||||
// Test aussie bank
|
||||
cfg.Exchanges[0].BankAccounts[0].Enabled = true
|
||||
cfg.Exchanges[0].BankAccounts[0].BankName = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].BankAddress = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].BankPostalCode = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].BankPostalCity = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].BankCountry = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].AccountName = testString
|
||||
cfg.Exchanges[0].BankAccounts[0].SupportedCurrencies = "AUD"
|
||||
cfg.Exchanges[0].BankAccounts[0].BSBNumber = "some BSB nonsense"
|
||||
cfg.Exchanges[0].BankAccounts[0].IBAN = ""
|
||||
cfg.Exchanges[0].BankAccounts[0].SWIFTCode = ""
|
||||
|
||||
err = cfg.CheckExchangeConfigValues()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !cfg.Exchanges[0].BankAccounts[0].Enabled {
|
||||
t.Fatal("bank account details provided this should not disable")
|
||||
}
|
||||
|
||||
cfg.Exchanges = nil
|
||||
cfg.Exchanges = append(cfg.Exchanges, cpy[0])
|
||||
|
||||
cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = nil
|
||||
cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = convert.BoolPtr(false)
|
||||
err = cfg.CheckExchangeConfigValues()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cfg.Exchanges[0].CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
err = cfg.CheckExchangeConfigValues()
|
||||
if err == nil {
|
||||
t.Error("err cannot be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrieveConfigCurrencyPairs(t *testing.T) {
|
||||
|
||||
@@ -449,7 +449,7 @@
|
||||
"websocketCapabilities": {}
|
||||
},
|
||||
"enabled": {
|
||||
"autoPairUpdates": false,
|
||||
"autoPairUpdates": true,
|
||||
"websocketAPI": false
|
||||
}
|
||||
},
|
||||
|
||||
248
currency/code.go
248
currency/code.go
@@ -6,8 +6,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
)
|
||||
|
||||
func (r Role) String() string {
|
||||
@@ -68,24 +66,24 @@ func (b *BaseCodes) HasData() bool {
|
||||
// GetFullCurrencyData returns a type that is read to dump to file
|
||||
func (b *BaseCodes) GetFullCurrencyData() (File, error) {
|
||||
var file File
|
||||
for _, i := range b.Items {
|
||||
switch i.Role {
|
||||
for i := range b.Items {
|
||||
switch b.Items[i].Role {
|
||||
case Unset:
|
||||
file.UnsetCurrency = append(file.UnsetCurrency, *i)
|
||||
file.UnsetCurrency = append(file.UnsetCurrency, *b.Items[i])
|
||||
case Fiat:
|
||||
file.FiatCurrency = append(file.FiatCurrency, *i)
|
||||
file.FiatCurrency = append(file.FiatCurrency, *b.Items[i])
|
||||
case Cryptocurrency:
|
||||
file.Cryptocurrency = append(file.Cryptocurrency, *i)
|
||||
file.Cryptocurrency = append(file.Cryptocurrency, *b.Items[i])
|
||||
case Token:
|
||||
file.Token = append(file.Token, *i)
|
||||
file.Token = append(file.Token, *b.Items[i])
|
||||
case Contract:
|
||||
file.Contracts = append(file.Contracts, *i)
|
||||
file.Contracts = append(file.Contracts, *b.Items[i])
|
||||
default:
|
||||
return file, errors.New("role undefined")
|
||||
}
|
||||
}
|
||||
|
||||
file.LastMainUpdate = b.LastMainUpdate
|
||||
file.LastMainUpdate = b.LastMainUpdate.Unix()
|
||||
return file, nil
|
||||
}
|
||||
|
||||
@@ -103,50 +101,11 @@ func (b *BaseCodes) GetCurrencies() Currencies {
|
||||
return currencies
|
||||
}
|
||||
|
||||
// UpdateCryptocurrency updates or registers a cryptocurrency
|
||||
func (b *BaseCodes) UpdateCryptocurrency(fullName, symbol string, id int) error {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
if b.Items[i].Symbol != symbol {
|
||||
continue
|
||||
}
|
||||
if b.Items[i].Role != Unset {
|
||||
if b.Items[i].Role != Cryptocurrency {
|
||||
if b.Items[i].FullName != "" {
|
||||
if b.Items[i].FullName != fullName {
|
||||
// multiple symbols found, break this and add the
|
||||
// full context - this most likely won't occur for
|
||||
// fiat but could occur for contracts.
|
||||
break
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("role already defined in cryptocurrency %s as [%s]",
|
||||
b.Items[i].Symbol,
|
||||
b.Items[i].Role)
|
||||
}
|
||||
b.Items[i].FullName = fullName
|
||||
b.Items[i].ID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Items[i].Role = Cryptocurrency
|
||||
b.Items[i].FullName = fullName
|
||||
b.Items[i].ID = id
|
||||
return nil
|
||||
// UpdateCurrency updates or registers a currency/contract
|
||||
func (b *BaseCodes) UpdateCurrency(fullName, symbol, blockchain string, id int, r Role) error {
|
||||
if r == Unset {
|
||||
return fmt.Errorf("role cannot be unset in update currency for %s", symbol)
|
||||
}
|
||||
|
||||
b.Items = append(b.Items, &Item{
|
||||
FullName: fullName,
|
||||
Symbol: symbol,
|
||||
ID: id,
|
||||
Role: Cryptocurrency,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateFiatCurrency updates or registers a fiat currency
|
||||
func (b *BaseCodes) UpdateFiatCurrency(fullName, symbol string, id int) error {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
@@ -154,115 +113,31 @@ func (b *BaseCodes) UpdateFiatCurrency(fullName, symbol string, id int) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if b.Items[i].Role != Unset {
|
||||
if b.Items[i].Role != Fiat {
|
||||
return fmt.Errorf("role already defined in fiat currency %s as [%s]",
|
||||
b.Items[i].Symbol,
|
||||
b.Items[i].Role)
|
||||
}
|
||||
if b.Items[i].Role == Unset {
|
||||
b.Items[i].FullName = fullName
|
||||
b.Items[i].Role = r
|
||||
b.Items[i].AssocChain = blockchain
|
||||
b.Items[i].ID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Items[i].Role = Fiat
|
||||
if b.Items[i].Role != r {
|
||||
// Captures same name currencies and duplicates to different roles
|
||||
break
|
||||
}
|
||||
|
||||
b.Items[i].FullName = fullName
|
||||
b.Items[i].AssocChain = blockchain
|
||||
b.Items[i].ID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Items = append(b.Items, &Item{
|
||||
FullName: fullName,
|
||||
Symbol: symbol,
|
||||
ID: id,
|
||||
Role: Fiat,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateToken updates or registers a token
|
||||
func (b *BaseCodes) UpdateToken(fullName, symbol, assocBlockchain string, id int) error {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
if b.Items[i].Symbol != symbol {
|
||||
continue
|
||||
}
|
||||
|
||||
if b.Items[i].Role != Unset {
|
||||
if b.Items[i].Role != Token {
|
||||
if b.Items[i].FullName != "" {
|
||||
if b.Items[i].FullName != fullName {
|
||||
// multiple symbols found, break this and add the
|
||||
// full context - this most likely won't occur for
|
||||
// fiat but could occur for contracts.
|
||||
break
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("role already defined in token %s as [%s]",
|
||||
b.Items[i].Symbol,
|
||||
b.Items[i].Role)
|
||||
}
|
||||
b.Items[i].FullName = fullName
|
||||
b.Items[i].ID = id
|
||||
b.Items[i].AssocChain = assocBlockchain
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Items[i].Role = Token
|
||||
b.Items[i].FullName = fullName
|
||||
b.Items[i].ID = id
|
||||
b.Items[i].AssocChain = assocBlockchain
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Items = append(b.Items, &Item{
|
||||
FullName: fullName,
|
||||
Symbol: symbol,
|
||||
FullName: fullName,
|
||||
Role: r,
|
||||
AssocChain: blockchain,
|
||||
ID: id,
|
||||
Role: Token,
|
||||
AssocChain: assocBlockchain,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateContract updates or registers a contract
|
||||
func (b *BaseCodes) UpdateContract(fullName, symbol, assocExchange string) error {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
if b.Items[i].Symbol != symbol {
|
||||
continue
|
||||
}
|
||||
|
||||
if b.Items[i].Role != Unset {
|
||||
if b.Items[i].Role != Contract {
|
||||
return fmt.Errorf("role already defined in contract %s as [%s]",
|
||||
b.Items[i].Symbol,
|
||||
b.Items[i].Role)
|
||||
}
|
||||
b.Items[i].FullName = fullName
|
||||
if !common.StringDataContains(b.Items[i].AssocExchange, assocExchange) {
|
||||
b.Items[i].AssocExchange = append(b.Items[i].AssocExchange,
|
||||
assocExchange)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Items[i].Role = Contract
|
||||
b.Items[i].FullName = fullName
|
||||
if !common.StringDataContains(b.Items[i].AssocExchange, assocExchange) {
|
||||
b.Items[i].AssocExchange = append(b.Items[i].AssocExchange,
|
||||
assocExchange)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Items = append(b.Items, &Item{
|
||||
FullName: fullName,
|
||||
Symbol: symbol,
|
||||
Role: Contract,
|
||||
AssocExchange: []string{assocExchange},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -293,42 +168,38 @@ func (b *BaseCodes) Register(c string) Code {
|
||||
}
|
||||
}
|
||||
|
||||
newItem := Item{Symbol: NewUpperCode}
|
||||
newCode := Code{
|
||||
Item: &newItem,
|
||||
newItem := &Item{Symbol: NewUpperCode}
|
||||
b.Items = append(b.Items, newItem)
|
||||
|
||||
return Code{
|
||||
Item: newItem,
|
||||
UpperCase: format,
|
||||
}
|
||||
|
||||
b.Items = append(b.Items, newCode.Item)
|
||||
return newCode
|
||||
}
|
||||
|
||||
// RegisterFiat registers a fiat currency from a string and returns a currency
|
||||
// code
|
||||
func (b *BaseCodes) RegisterFiat(c string) (Code, error) {
|
||||
func (b *BaseCodes) RegisterFiat(c string) Code {
|
||||
c = strings.ToUpper(c)
|
||||
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
if b.Items[i].Symbol == c {
|
||||
if b.Items[i].Role != Unset {
|
||||
if b.Items[i].Role != Fiat {
|
||||
return Code{}, fmt.Errorf("register fiat error role already defined in fiat %s as [%s]",
|
||||
b.Items[i].Symbol,
|
||||
b.Items[i].Role)
|
||||
}
|
||||
return Code{Item: b.Items[i], UpperCase: true}, nil
|
||||
if b.Items[i].Role == Unset {
|
||||
b.Items[i].Role = Fiat
|
||||
}
|
||||
b.Items[i].Role = Fiat
|
||||
return Code{Item: b.Items[i], UpperCase: true}, nil
|
||||
|
||||
if b.Items[i].Role != Fiat {
|
||||
continue
|
||||
}
|
||||
return Code{Item: b.Items[i], UpperCase: true}
|
||||
}
|
||||
}
|
||||
|
||||
item := &Item{Symbol: c, Role: Fiat}
|
||||
b.Items = append(b.Items, item)
|
||||
|
||||
return Code{Item: item, UpperCase: true}, nil
|
||||
return Code{Item: item, UpperCase: true}
|
||||
}
|
||||
|
||||
// LoadItem sets item data
|
||||
@@ -336,41 +207,18 @@ func (b *BaseCodes) LoadItem(item *Item) error {
|
||||
b.mtx.Lock()
|
||||
defer b.mtx.Unlock()
|
||||
for i := range b.Items {
|
||||
if b.Items[i].Symbol == item.Symbol {
|
||||
if b.Items[i].Role == Unset {
|
||||
b.Items[i].AssocChain = item.AssocChain
|
||||
b.Items[i].AssocExchange = item.AssocExchange
|
||||
b.Items[i].ID = item.ID
|
||||
b.Items[i].Role = item.Role
|
||||
b.Items[i].FullName = item.FullName
|
||||
return nil
|
||||
}
|
||||
|
||||
if b.Items[i].FullName != "" {
|
||||
if b.Items[i].FullName == item.FullName {
|
||||
b.Items[i].AssocChain = item.AssocChain
|
||||
b.Items[i].AssocExchange = item.AssocExchange
|
||||
b.Items[i].ID = item.ID
|
||||
b.Items[i].Role = item.Role
|
||||
return nil
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if b.Items[i].ID == item.ID {
|
||||
b.Items[i].AssocChain = item.AssocChain
|
||||
b.Items[i].AssocExchange = item.AssocExchange
|
||||
b.Items[i].FullName = item.FullName
|
||||
b.Items[i].ID = item.ID
|
||||
b.Items[i].Role = item.Role
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("currency %s not found in currencycode list",
|
||||
item.Symbol)
|
||||
if b.Items[i].Symbol != item.Symbol ||
|
||||
(b.Items[i].Role != Unset &&
|
||||
item.Role != Unset &&
|
||||
b.Items[i].Role != item.Role) {
|
||||
continue
|
||||
}
|
||||
b.Items[i].AssocChain = item.AssocChain
|
||||
b.Items[i].ID = item.ID
|
||||
b.Items[i].Role = item.Role
|
||||
b.Items[i].FullName = item.FullName
|
||||
return nil
|
||||
}
|
||||
|
||||
b.Items = append(b.Items, item)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -176,41 +176,51 @@ func TestBaseCode(t *testing.T) {
|
||||
false)
|
||||
}
|
||||
|
||||
err := main.UpdateContract("Bitcoin Perpetual", "XBTUSD", "Bitmex")
|
||||
main.Register("XBTUSD")
|
||||
|
||||
err := main.UpdateCurrency("Bitcoin Perpetual",
|
||||
"XBTUSD",
|
||||
"",
|
||||
0,
|
||||
Contract)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = main.UpdateCryptocurrency("Bitcoin", "BTC", 1337)
|
||||
main.Register("BTC")
|
||||
err = main.UpdateCurrency("Bitcoin", "BTC", "", 1337, Cryptocurrency)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = main.UpdateFiatCurrency("Unreal Dollar", "AUD", 1111)
|
||||
main.Register("AUD")
|
||||
err = main.UpdateCurrency("Unreal Dollar", "AUD", "", 1111, Fiat)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if main.Items[5].FullName != "Unreal Dollar" {
|
||||
t.Error("Expected fullname to update for AUD")
|
||||
}
|
||||
|
||||
err = main.UpdateFiatCurrency("Australian Dollar", "AUD", 1336)
|
||||
err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
main.Items[5].Role = Unset
|
||||
err = main.UpdateFiatCurrency("Australian Dollar", "AUD", 1336)
|
||||
err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if main.Items[5].Role != Fiat {
|
||||
t.Error("Expected role to change to Fiat")
|
||||
}
|
||||
|
||||
err = main.UpdateToken("Populous", "PPT", "ETH", 1335)
|
||||
main.Register("PPT")
|
||||
err = main.UpdateCurrency("Populous", "PPT", "ETH", 1335, Token)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
contract := main.Register("XBTUSD")
|
||||
@@ -235,15 +245,12 @@ func TestBaseCode(t *testing.T) {
|
||||
true)
|
||||
}
|
||||
|
||||
err = main.LoadItem(&Item{
|
||||
main.LoadItem(&Item{
|
||||
ID: 0,
|
||||
FullName: "Cardano",
|
||||
Role: Cryptocurrency,
|
||||
Symbol: "ADA",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error("BaseCode LoadItem() error", err)
|
||||
}
|
||||
|
||||
full, err := main.GetFullCurrencyData()
|
||||
if err != nil {
|
||||
@@ -275,8 +282,8 @@ func TestBaseCode(t *testing.T) {
|
||||
len(full.UnsetCurrency))
|
||||
}
|
||||
|
||||
if !full.LastMainUpdate.IsZero() {
|
||||
t.Errorf("BaseCode GetFullCurrencyData() error expected 0 but received %s",
|
||||
if full.LastMainUpdate.(int64) != -62135596800 {
|
||||
t.Errorf("BaseCode GetFullCurrencyData() error expected -62135596800 but received %d",
|
||||
full.LastMainUpdate)
|
||||
}
|
||||
|
||||
@@ -295,41 +302,35 @@ func TestBaseCode(t *testing.T) {
|
||||
}
|
||||
|
||||
main.Items[0].FullName = "Hello"
|
||||
err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338)
|
||||
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if main.Items[0].FullName != "MEWOW" {
|
||||
t.Error("Fullname not updated")
|
||||
}
|
||||
err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338)
|
||||
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = main.UpdateCurrency("WOWCATS", "CATS", "", 3, Token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
main.Items[0].Role = Cryptocurrency
|
||||
err = main.UpdateCryptocurrency("MEWOW", "CATS", 3)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
}
|
||||
if main.Items[0].ID != 3 {
|
||||
// Creates a new item under a different currency role
|
||||
if main.Items[9].ID != 3 {
|
||||
t.Error("ID not updated")
|
||||
}
|
||||
|
||||
main.Items[0].Role = Unset
|
||||
err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338)
|
||||
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency)
|
||||
if err != nil {
|
||||
t.Error("BaseCode UpdateContract error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if main.Items[0].ID != 1338 {
|
||||
t.Error("ID not updated")
|
||||
}
|
||||
|
||||
main.Items[0].Role = Token
|
||||
err = main.UpdateCryptocurrency("MEWOW", "CATS", 3)
|
||||
if err == nil {
|
||||
t.Error("Expecting cryptocurrency to already exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodeString(t *testing.T) {
|
||||
|
||||
@@ -41,12 +41,11 @@ type Code struct {
|
||||
// Item defines a sub type containing the main attributes of a designated
|
||||
// currency code pointer
|
||||
type Item struct {
|
||||
ID int `json:"id"`
|
||||
FullName string `json:"fullName"`
|
||||
Symbol string `json:"symbol"`
|
||||
Role Role `json:"role"`
|
||||
AssocChain string `json:"associatedBlockchain"`
|
||||
AssocExchange []string `json:"associatedExchanges"`
|
||||
ID int `json:"id,omitempty"`
|
||||
FullName string `json:"fullName,omitempty"`
|
||||
Symbol string `json:"symbol"`
|
||||
Role Role `json:"-"`
|
||||
AssocChain string `json:"associatedBlockchain,omitempty"`
|
||||
}
|
||||
|
||||
// Const declarations for individual currencies/tokens/fiat
|
||||
|
||||
@@ -8,7 +8,6 @@ package coinmarketcap
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
// SetDefaults sets default values for the exchange
|
||||
@@ -62,8 +62,8 @@ func (c *Coinmarketcap) GetCryptocurrencyInfo(currencyID ...int64) (CryptoCurren
|
||||
}
|
||||
|
||||
var currStr []string
|
||||
for _, d := range currencyID {
|
||||
currStr = append(currStr, strconv.FormatInt(d, 10))
|
||||
for i := range currencyID {
|
||||
currStr = append(currStr, strconv.FormatInt(currencyID[i], 10))
|
||||
}
|
||||
|
||||
val := url.Values{}
|
||||
@@ -667,7 +667,6 @@ func (c *Coinmarketcap) GetPriceConversion(amount float64, currencyID int64, atH
|
||||
func (c *Coinmarketcap) SendHTTPRequest(method, endpoint string, v url.Values, result interface{}) error {
|
||||
headers := make(map[string]string)
|
||||
headers["Accept"] = "application/json"
|
||||
headers["Accept-Encoding"] = "deflate, gzip"
|
||||
headers["X-CMC_PRO_API_KEY"] = c.APIkey
|
||||
|
||||
path := c.APIUrl + c.APIVersion + endpoint
|
||||
@@ -710,7 +709,8 @@ func (c *Coinmarketcap) SetAccountPlan(s string) error {
|
||||
case "enterprise":
|
||||
c.Plan = Enterprise
|
||||
default:
|
||||
return fmt.Errorf("account plan %s not found", s)
|
||||
log.Warnf(log.Global, "account plan %s not found, defaulting to basic", s)
|
||||
c.Plan = Basic
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,11 +44,6 @@ func TestSetup(t *testing.T) {
|
||||
if err := c.Setup(cfg); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cfg.AccountPlan = "meow"
|
||||
if err := c.Setup(cfg); err == nil {
|
||||
t.Error("expected err when invalid account plan is specified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAccountPlan(t *testing.T) {
|
||||
@@ -405,8 +400,4 @@ func TestSetAccountPlan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.SetAccountPlan("bra"); err == nil {
|
||||
t.Error("SetAccountPlan() error cannot be nil")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,22 +110,14 @@ func (c *ConversionRates) Update(m map[string]float64) error {
|
||||
var list []Code // Verification list, cross check all currencies coming in
|
||||
|
||||
var mainBaseCurrency Code
|
||||
|
||||
for key, val := range m {
|
||||
code1, err := storage.ValidateFiatCode(key[:3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
code1 := storage.ValidateFiatCode(key[:3])
|
||||
|
||||
if mainBaseCurrency == (Code{}) {
|
||||
mainBaseCurrency = code1
|
||||
}
|
||||
|
||||
code2, err := storage.ValidateFiatCode(key[3:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
code2 := storage.ValidateFiatCode(key[3:])
|
||||
if code1 == code2 { // Get rid of same conversions
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ type Currencies []Code
|
||||
// Strings returns an array of currency strings
|
||||
func (c Currencies) Strings() []string {
|
||||
var list []string
|
||||
for _, d := range c {
|
||||
list = append(list, d.String())
|
||||
for i := range c {
|
||||
list = append(list, c[i].String())
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -53,8 +53,9 @@ func (c *Currencies) UnmarshalJSON(d []byte) error {
|
||||
}
|
||||
|
||||
var allTheCurrencies Currencies
|
||||
for _, data := range strings.Split(configCurrencies, ",") {
|
||||
allTheCurrencies = append(allTheCurrencies, NewCode(data))
|
||||
curr := strings.Split(configCurrencies, ",")
|
||||
for i := range curr {
|
||||
allTheCurrencies = append(allTheCurrencies, NewCode(curr[i]))
|
||||
}
|
||||
|
||||
*c = allTheCurrencies
|
||||
@@ -72,17 +73,14 @@ func (c Currencies) Match(other Currencies) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, d := range c {
|
||||
var found bool
|
||||
for i := range other {
|
||||
if d == other[i] {
|
||||
found = true
|
||||
break
|
||||
match:
|
||||
for x := range c {
|
||||
for y := range other {
|
||||
if c[x] == other[y] {
|
||||
continue match
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -47,3 +47,18 @@ func TestCurrenciesMarshalJSON(t *testing.T) {
|
||||
expected, string(encoded))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
matchString := []string{"btc", "usd", "ltc", "bro", "things"}
|
||||
c := NewCurrenciesFromStringArray(matchString)
|
||||
|
||||
if !c.Match(NewCurrenciesFromStringArray(matchString)) {
|
||||
t.Fatal("should match")
|
||||
}
|
||||
if c.Match(NewCurrenciesFromStringArray([]string{"btc", "usd", "ltc", "bro"})) {
|
||||
t.Fatal("should not match")
|
||||
}
|
||||
if c.Match(NewCurrenciesFromStringArray([]string{"btc", "usd", "ltc", "bro", "garbo"})) {
|
||||
t.Fatal("should not match")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,9 +71,14 @@ func GetTotalMarketCryptocurrencies() ([]Code, error) {
|
||||
return storage.GetTotalMarketCryptocurrencies()
|
||||
}
|
||||
|
||||
// RunStorageUpdater runs a new foreign exchange updater instance
|
||||
func RunStorageUpdater(o BotOverrides, m *MainConfiguration, filepath string, v bool) error {
|
||||
return storage.RunUpdater(o, m, filepath, v)
|
||||
// RunStorageUpdater runs a new foreign exchange updater instance
|
||||
func RunStorageUpdater(o BotOverrides, m *MainConfiguration, filepath string) error {
|
||||
return storage.RunUpdater(o, m, filepath)
|
||||
}
|
||||
|
||||
// ShutdownStorageUpdater cleanly shuts down and saves to currency.json
|
||||
func ShutdownStorageUpdater() error {
|
||||
return storage.Shutdown()
|
||||
}
|
||||
|
||||
// CopyPairFormat copies the pair format from a list of pairs once matched
|
||||
@@ -100,17 +105,23 @@ func FormatPairs(pairs []string, delimiter, index string) (Pairs, error) {
|
||||
continue
|
||||
}
|
||||
var p Pair
|
||||
var err error
|
||||
if delimiter != "" {
|
||||
p = NewPairDelimiter(pairs[x], delimiter)
|
||||
p, err = NewPairDelimiter(pairs[x], delimiter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if index != "" {
|
||||
var err error
|
||||
p, err = NewPairFromIndex(pairs[x], index)
|
||||
if err != nil {
|
||||
return Pairs{}, err
|
||||
}
|
||||
} else {
|
||||
p = NewPairFromStrings(pairs[x][0:3], pairs[x][3:])
|
||||
p, err = NewPairFromStrings(pairs[x][0:3], pairs[x][3:])
|
||||
if err != nil {
|
||||
return Pairs{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
result = append(result, p)
|
||||
|
||||
@@ -53,10 +53,24 @@ type FXSettings struct {
|
||||
// File defines a full currency file generated by the currency storage
|
||||
// analysis system
|
||||
type File struct {
|
||||
LastMainUpdate time.Time `json:"lastMainUpdate"`
|
||||
Cryptocurrency []Item `json:"cryptocurrencies"`
|
||||
FiatCurrency []Item `json:"fiatCurrencies"`
|
||||
UnsetCurrency []Item `json:"unsetCurrencies"`
|
||||
Contracts []Item `json:"contracts"`
|
||||
Token []Item `json:"tokens"`
|
||||
LastMainUpdate interface{} `json:"lastMainUpdate"`
|
||||
Cryptocurrency []Item `json:"cryptocurrencies"`
|
||||
FiatCurrency []Item `json:"fiatCurrencies"`
|
||||
UnsetCurrency []Item `json:"unsetCurrencies"`
|
||||
Contracts []Item `json:"contracts"`
|
||||
Token []Item `json:"tokens"`
|
||||
}
|
||||
|
||||
// Const here are packaged defined delimiters
|
||||
const (
|
||||
UnderscoreDelimiter = "_"
|
||||
DashDelimiter = "-"
|
||||
ForwardSlashDelimiter = "/"
|
||||
ColonDelimiter = ":"
|
||||
)
|
||||
|
||||
// delimiters is a delimiter list
|
||||
var delimiters = []string{UnderscoreDelimiter,
|
||||
DashDelimiter,
|
||||
ForwardSlashDelimiter,
|
||||
ColonDelimiter}
|
||||
|
||||
@@ -2,14 +2,16 @@ package currency
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
// GetAssetTypes returns a list of stored asset types
|
||||
func (p *PairsManager) GetAssetTypes() asset.Items {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
var assetTypes asset.Items
|
||||
for k := range p.Pairs {
|
||||
assetTypes = append(assetTypes, k)
|
||||
@@ -18,28 +20,23 @@ func (p *PairsManager) GetAssetTypes() asset.Items {
|
||||
}
|
||||
|
||||
// Get gets the currency pair config based on the asset type
|
||||
func (p *PairsManager) Get(a asset.Item) *PairStore {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
func (p *PairsManager) Get(a asset.Item) (*PairStore, error) {
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
c, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return nil
|
||||
return nil,
|
||||
fmt.Errorf("cannot get pair store, asset type %s not supported", a)
|
||||
}
|
||||
return c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Store stores a new currency pair config based on its asset type
|
||||
func (p *PairsManager) Store(a asset.Item, ps PairStore) {
|
||||
p.m.Lock()
|
||||
|
||||
if p.Pairs == nil {
|
||||
p.Pairs = make(map[asset.Item]*PairStore)
|
||||
}
|
||||
|
||||
if !p.AssetTypes.Contains(a) {
|
||||
p.AssetTypes = append(p.AssetTypes, a)
|
||||
}
|
||||
|
||||
p.Pairs[a] = &ps
|
||||
p.m.Unlock()
|
||||
}
|
||||
@@ -51,37 +48,35 @@ func (p *PairsManager) Delete(a asset.Item) {
|
||||
if p.Pairs == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
delete(p.Pairs, a)
|
||||
}
|
||||
|
||||
// GetPairs gets a list of stored pairs based on the asset type and whether
|
||||
// they're enabled or not
|
||||
func (p *PairsManager) GetPairs(a asset.Item, enabled bool) Pairs {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
func (p *PairsManager) GetPairs(a asset.Item, enabled bool) (Pairs, error) {
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
if p.Pairs == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
c, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var pairs Pairs
|
||||
if enabled {
|
||||
pairs = c.Enabled
|
||||
} else {
|
||||
pairs = c.Available
|
||||
for i := range c.Enabled {
|
||||
if !c.Available.Contains(c.Enabled[i], true) {
|
||||
return c.Enabled,
|
||||
fmt.Errorf("enabled pair %s of asset type %s not contained in available list",
|
||||
c.Enabled[i],
|
||||
a)
|
||||
}
|
||||
}
|
||||
return c.Enabled, nil
|
||||
}
|
||||
|
||||
return pairs
|
||||
return c.Available, nil
|
||||
}
|
||||
|
||||
// StorePairs stores a list of pairs based on the asset type and whether
|
||||
@@ -96,7 +91,8 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) {
|
||||
|
||||
c, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
c = new(PairStore)
|
||||
p.Pairs[a] = new(PairStore)
|
||||
c = p.Pairs[a]
|
||||
}
|
||||
|
||||
if enabled {
|
||||
@@ -104,8 +100,6 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) {
|
||||
} else {
|
||||
c.Available = pairs
|
||||
}
|
||||
|
||||
p.Pairs[a] = c
|
||||
}
|
||||
|
||||
// DisablePair removes the pair from the enabled pairs list if found
|
||||
@@ -113,17 +107,9 @@ func (p *PairsManager) DisablePair(a asset.Item, pair Pair) error {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.Pairs == nil {
|
||||
return errors.New("pair manager not initialised")
|
||||
}
|
||||
|
||||
c, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return errors.New("asset type not found")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return errors.New("currency store is nil")
|
||||
c, err := p.getPairStore(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.Enabled.Contains(pair, true) {
|
||||
@@ -140,27 +126,82 @@ func (p *PairsManager) EnablePair(a asset.Item, pair Pair) error {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.Pairs == nil {
|
||||
return errors.New("pair manager not initialised")
|
||||
}
|
||||
|
||||
c, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return errors.New("asset type not found")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return errors.New("currency store is nil")
|
||||
c, err := p.getPairStore(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.Available.Contains(pair, true) {
|
||||
return errors.New("specified pair was not found in the list of available pairs")
|
||||
return fmt.Errorf("%s pair was not found in the list of available pairs",
|
||||
pair)
|
||||
}
|
||||
|
||||
if c.Enabled.Contains(pair, true) {
|
||||
return errors.New("specified pair is already enabled")
|
||||
return fmt.Errorf("%s pair is already enabled", pair)
|
||||
}
|
||||
|
||||
c.Enabled = c.Enabled.Add(pair)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAssetEnabled checks to see if an asset is enabled
|
||||
func (p *PairsManager) IsAssetEnabled(a asset.Item) error {
|
||||
p.m.RLock()
|
||||
defer p.m.RUnlock()
|
||||
|
||||
c, err := p.getPairStore(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.AssetEnabled == nil {
|
||||
return errors.New("cannot ascertain if asset is enabled, variable is nil")
|
||||
}
|
||||
|
||||
if !*c.AssetEnabled {
|
||||
return errors.New("asset not enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAssetEnabled sets if an asset is enabled or disabled for first run
|
||||
func (p *PairsManager) SetAssetEnabled(a asset.Item, enabled bool) error {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
c, err := p.getPairStore(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.AssetEnabled == nil {
|
||||
c.AssetEnabled = convert.BoolPtr(enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !*c.AssetEnabled && !enabled {
|
||||
return errors.New("asset already disabled")
|
||||
} else if *c.AssetEnabled && enabled {
|
||||
return errors.New("asset already enabled")
|
||||
}
|
||||
|
||||
*c.AssetEnabled = enabled
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PairsManager) getPairStore(a asset.Item) (*PairStore, error) {
|
||||
if p.Pairs == nil {
|
||||
return nil, errors.New("pair manager not initialised")
|
||||
}
|
||||
|
||||
c, ok := p.Pairs[a]
|
||||
if !ok {
|
||||
return nil, errors.New("asset type not found")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return nil, errors.New("currency store is nil")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -8,11 +8,21 @@ import (
|
||||
|
||||
var p PairsManager
|
||||
|
||||
func initTest() {
|
||||
func initTest(t *testing.T) {
|
||||
spotAvailable, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
spotEnabled, err := NewPairsFromStrings([]string{"BTC-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p.Store(asset.Spot,
|
||||
PairStore{
|
||||
Available: NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}),
|
||||
Enabled: NewPairsFromStrings([]string{"BTC-USD"}),
|
||||
Available: spotAvailable,
|
||||
Enabled: spotEnabled,
|
||||
RequestFormat: &PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
@@ -25,7 +35,7 @@ func initTest() {
|
||||
}
|
||||
|
||||
func TestGetAssetTypes(t *testing.T) {
|
||||
initTest()
|
||||
initTest(t)
|
||||
|
||||
a := p.GetAssetTypes()
|
||||
if len(a) == 0 {
|
||||
@@ -38,22 +48,34 @@ func TestGetAssetTypes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
initTest()
|
||||
initTest(t)
|
||||
|
||||
if p.Get(asset.Spot) == nil {
|
||||
t.Error("Spot assets shouldn't be nil")
|
||||
_, err := p.Get(asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if p.Get(asset.Futures) != nil {
|
||||
_, err = p.Get(asset.Futures)
|
||||
if err == nil {
|
||||
t.Error("Futures should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
availPairs, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
enabledPairs, err := NewPairsFromStrings([]string{"BTC-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p.Store(asset.Futures,
|
||||
PairStore{
|
||||
Available: NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}),
|
||||
Enabled: NewPairsFromStrings([]string{"BTC-USD"}),
|
||||
Available: availPairs,
|
||||
Enabled: enabledPairs,
|
||||
RequestFormat: &PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
@@ -64,7 +86,12 @@ func TestStore(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
if p.Get(asset.Futures) == nil {
|
||||
f, err := p.Get(asset.Futures)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
t.Error("Futures assets shouldn't be nil")
|
||||
}
|
||||
}
|
||||
@@ -73,67 +100,131 @@ func TestDelete(t *testing.T) {
|
||||
p.Pairs = nil
|
||||
p.Delete(asset.Spot)
|
||||
|
||||
p.Store(asset.Spot,
|
||||
PairStore{
|
||||
Available: NewPairsFromStrings([]string{"BTC-USD"}),
|
||||
},
|
||||
)
|
||||
btcusdPairs, err := NewPairsFromStrings([]string{"BTC-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p.Store(asset.Spot, PairStore{
|
||||
Available: btcusdPairs,
|
||||
})
|
||||
|
||||
p.Delete(asset.UpsideProfitContract)
|
||||
if p.Get(asset.Spot) == nil {
|
||||
spotPS, err := p.Get(asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if spotPS == nil {
|
||||
t.Error("AssetTypeSpot should exist")
|
||||
}
|
||||
|
||||
p.Delete(asset.Spot)
|
||||
if p.Get(asset.Spot) != nil {
|
||||
|
||||
if _, err := p.Get(asset.Spot); err == nil {
|
||||
t.Error("Delete should have deleted AssetTypeSpot")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPairs(t *testing.T) {
|
||||
p.Pairs = nil
|
||||
pairs := p.GetPairs(asset.Spot, true)
|
||||
pairs, err := p.GetPairs(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pairs != nil {
|
||||
t.Fatal("pairs shouldn't be populated")
|
||||
}
|
||||
|
||||
initTest()
|
||||
pairs = p.GetPairs(asset.Spot, true)
|
||||
initTest(t)
|
||||
pairs, err = p.GetPairs(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pairs == nil {
|
||||
t.Fatal("pairs should be populated")
|
||||
}
|
||||
|
||||
pairs = p.GetPairs("blah", true)
|
||||
pairs, err = p.GetPairs("blah", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pairs != nil {
|
||||
t.Fatal("pairs shouldn't be populated")
|
||||
}
|
||||
|
||||
superfluous := NewPair(DASH, USDT)
|
||||
newPairs := p.Pairs[asset.Spot].Enabled.Add(superfluous)
|
||||
p.Pairs[asset.Spot].Enabled = newPairs
|
||||
|
||||
_, err = p.GetPairs(asset.Spot, true)
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorePairs(t *testing.T) {
|
||||
p.Pairs = nil
|
||||
p.StorePairs(asset.Spot, NewPairsFromStrings([]string{"ETH-USD"}), false)
|
||||
pairs := p.GetPairs(asset.Spot, false)
|
||||
if !pairs.Contains(NewPairFromString("ETH-USD"), true) {
|
||||
|
||||
ethusdPairs, err := NewPairsFromStrings([]string{"ETH-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p.StorePairs(asset.Spot, ethusdPairs, false)
|
||||
pairs, err := p.GetPairs(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ethusd, err := NewPairFromString("ETH-USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !pairs.Contains(ethusd, true) {
|
||||
t.Errorf("TestStorePairs failed, unexpected result")
|
||||
}
|
||||
|
||||
initTest()
|
||||
p.StorePairs(asset.Spot, NewPairsFromStrings([]string{"ETH-USD"}), false)
|
||||
pairs = p.GetPairs(asset.Spot, false)
|
||||
initTest(t)
|
||||
p.StorePairs(asset.Spot, ethusdPairs, false)
|
||||
pairs, err = p.GetPairs(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pairs == nil {
|
||||
t.Errorf("pairs should be populated")
|
||||
}
|
||||
|
||||
if !pairs.Contains(NewPairFromString("ETH-USD"), true) {
|
||||
if !pairs.Contains(ethusd, true) {
|
||||
t.Errorf("TestStorePairs failed, unexpected result")
|
||||
}
|
||||
|
||||
p.StorePairs(asset.Futures, NewPairsFromStrings([]string{"ETH-KRW"}), true)
|
||||
pairs = p.GetPairs(asset.Futures, true)
|
||||
ethkrwPairs, err := NewPairsFromStrings([]string{"ETH-KRW"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
p.StorePairs(asset.Futures, ethkrwPairs, true)
|
||||
p.StorePairs(asset.Futures, ethkrwPairs, false)
|
||||
pairs, err = p.GetPairs(asset.Futures, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if pairs == nil {
|
||||
t.Errorf("pairs futures should be populated")
|
||||
}
|
||||
|
||||
if !pairs.Contains(NewPairFromString("ETH-KRW"), true) {
|
||||
ethkrw, err := NewPairFromString("ETH-KRW")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !pairs.Contains(ethkrw, true) {
|
||||
t.Errorf("TestStorePairs failed, unexpected result")
|
||||
}
|
||||
}
|
||||
@@ -146,7 +237,7 @@ func TestDisablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test asset type which doesn't exist
|
||||
initTest()
|
||||
initTest(t)
|
||||
if err := p.DisablePair(asset.Futures, Pair{}); err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -158,7 +249,7 @@ func TestDisablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test disabling a pair which isn't enabled
|
||||
initTest()
|
||||
initTest(t)
|
||||
if err := p.DisablePair(asset.Spot, NewPair(LTC, USD)); err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -177,7 +268,7 @@ func TestEnablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test asset type which doesn't exist
|
||||
initTest()
|
||||
initTest(t)
|
||||
if err := p.EnablePair(asset.Futures, Pair{}); err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -189,7 +280,7 @@ func TestEnablePair(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test enabling a pair which isn't in the list of available pairs
|
||||
initTest()
|
||||
initTest(t)
|
||||
if err := p.EnablePair(asset.Spot, NewPair(ETH, USD)); err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
@@ -204,3 +295,55 @@ func TestEnablePair(t *testing.T) {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAssetEnabled_SetAssetEnabled(t *testing.T) {
|
||||
p.Pairs = nil
|
||||
// Test enabling a pair when the pair manager is not initialised
|
||||
err := p.IsAssetEnabled(asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
err = p.SetAssetEnabled(asset.Spot, true)
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
|
||||
// Test asset type which doesn't exist
|
||||
initTest(t)
|
||||
|
||||
err = p.IsAssetEnabled(asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
err = p.SetAssetEnabled(asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = p.SetAssetEnabled(asset.Spot, false)
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
|
||||
err = p.IsAssetEnabled(asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
err = p.SetAssetEnabled(asset.Spot, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = p.SetAssetEnabled(asset.Spot, true)
|
||||
if err == nil {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
|
||||
err = p.IsAssetEnabled(asset.Spot)
|
||||
if err != nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ type PairsManager struct {
|
||||
ConfigFormat *PairFormat `json:"configFormat,omitempty"`
|
||||
UseGlobalFormat bool `json:"useGlobalFormat,omitempty"`
|
||||
LastUpdated int64 `json:"lastUpdated,omitempty"`
|
||||
AssetTypes asset.Items `json:"assetTypes"`
|
||||
Pairs map[asset.Item]*PairStore `json:"pairs"`
|
||||
m sync.Mutex
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
// PairStore stores a currency pair store
|
||||
type PairStore struct {
|
||||
AssetEnabled *bool `json:"assetEnabled"`
|
||||
Enabled Pairs `json:"enabled"`
|
||||
Available Pairs `json:"available"`
|
||||
RequestFormat *PairFormat `json:"requestFormat,omitempty"`
|
||||
|
||||
178
currency/pair.go
178
currency/pair.go
@@ -1,15 +1,24 @@
|
||||
package currency
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewPairDelimiter splits the desired currency string at delimeter, the returns
|
||||
// a Pair struct
|
||||
func NewPairDelimiter(currencyPair, delimiter string) Pair {
|
||||
func NewPairDelimiter(currencyPair, delimiter string) (Pair, error) {
|
||||
if !strings.Contains(currencyPair, delimiter) {
|
||||
return Pair{},
|
||||
fmt.Errorf("delimiter: [%s] not found in currencypair string", delimiter)
|
||||
}
|
||||
result := strings.Split(currencyPair, delimiter)
|
||||
if len(result) < 2 {
|
||||
return Pair{},
|
||||
fmt.Errorf("supplied pair: [%s] cannot be split with %s",
|
||||
currencyPair,
|
||||
delimiter)
|
||||
}
|
||||
if len(result) > 2 {
|
||||
result[1] = strings.Join(result[1:], delimiter)
|
||||
}
|
||||
@@ -17,15 +26,24 @@ func NewPairDelimiter(currencyPair, delimiter string) Pair {
|
||||
Delimiter: delimiter,
|
||||
Base: NewCode(result[0]),
|
||||
Quote: NewCode(result[1]),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewPairFromStrings returns a CurrencyPair without a delimiter
|
||||
func NewPairFromStrings(baseCurrency, quoteCurrency string) Pair {
|
||||
return Pair{
|
||||
Base: NewCode(baseCurrency),
|
||||
Quote: NewCode(quoteCurrency),
|
||||
func NewPairFromStrings(base, quote string) (Pair, error) {
|
||||
if strings.Contains(base, " ") {
|
||||
return Pair{},
|
||||
fmt.Errorf("cannot create pair, invalid base currency string [%s]",
|
||||
base)
|
||||
}
|
||||
|
||||
if strings.Contains(quote, " ") {
|
||||
return Pair{},
|
||||
fmt.Errorf("cannot create pair, invalid quote currency string [%s]",
|
||||
quote)
|
||||
}
|
||||
|
||||
return Pair{Base: NewCode(base), Quote: NewCode(quote)}, nil
|
||||
}
|
||||
|
||||
// NewPair returns a currency pair from currency codes
|
||||
@@ -55,23 +73,24 @@ func NewPairFromIndex(currencyPair, index string) (Pair, error) {
|
||||
}
|
||||
if i == 0 {
|
||||
return NewPairFromStrings(currencyPair[0:len(index)],
|
||||
currencyPair[len(index):]),
|
||||
nil
|
||||
currencyPair[len(index):])
|
||||
}
|
||||
return NewPairFromStrings(currencyPair[0:i], currencyPair[i:]), nil
|
||||
return NewPairFromStrings(currencyPair[0:i], currencyPair[i:])
|
||||
}
|
||||
|
||||
// NewPairFromString converts currency string into a new CurrencyPair
|
||||
// with or without delimeter
|
||||
func NewPairFromString(currencyPair string) Pair {
|
||||
delimiters := []string{"_", "-", "/", ":"}
|
||||
var delimiter string
|
||||
for _, x := range delimiters {
|
||||
if strings.Contains(currencyPair, x) {
|
||||
delimiter = x
|
||||
return NewPairDelimiter(currencyPair, delimiter)
|
||||
func NewPairFromString(currencyPair string) (Pair, error) {
|
||||
for x := range delimiters {
|
||||
if strings.Contains(currencyPair, delimiters[x]) {
|
||||
return NewPairDelimiter(currencyPair, delimiters[x])
|
||||
}
|
||||
}
|
||||
if len(currencyPair) < 3 {
|
||||
return Pair{},
|
||||
fmt.Errorf("cannot create pair from %s string",
|
||||
currencyPair)
|
||||
}
|
||||
return NewPairFromStrings(currencyPair[0:3], currencyPair[3:])
|
||||
}
|
||||
|
||||
@@ -79,129 +98,12 @@ func NewPairFromString(currencyPair string) Pair {
|
||||
// with a specific format. This is helpful for exchanges which
|
||||
// provide currency pairs with no delimiter so we can match it with a list and
|
||||
// apply the same format
|
||||
func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFormat) Pair {
|
||||
func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFormat) (Pair, error) {
|
||||
for x := range pairs {
|
||||
if strings.EqualFold(pairs[x].Format(pairFmt.Delimiter,
|
||||
pairFmt.Uppercase).String(), currencyPair) {
|
||||
return pairs[x]
|
||||
fPair := pairs[x].Format(pairFmt.Delimiter, pairFmt.Uppercase)
|
||||
if strings.EqualFold(fPair.String(), currencyPair) {
|
||||
return pairs[x], nil
|
||||
}
|
||||
}
|
||||
return NewPairFromString(currencyPair)
|
||||
}
|
||||
|
||||
// String returns a currency pair string
|
||||
func (p Pair) String() string {
|
||||
return p.Base.String() + p.Delimiter + p.Quote.String()
|
||||
}
|
||||
|
||||
// Lower converts the pair object to lowercase
|
||||
func (p Pair) Lower() Pair {
|
||||
return Pair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.Lower(),
|
||||
Quote: p.Quote.Lower(),
|
||||
}
|
||||
}
|
||||
|
||||
// Upper converts the pair object to uppercase
|
||||
func (p Pair) Upper() Pair {
|
||||
return Pair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.Upper(),
|
||||
Quote: p.Quote.Upper(),
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON comforms type to the umarshaler interface
|
||||
func (p *Pair) UnmarshalJSON(d []byte) error {
|
||||
var pair string
|
||||
err := json.Unmarshal(d, &pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*p = NewPairFromString(pair)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON conforms type to the marshaler interface
|
||||
func (p Pair) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.String())
|
||||
}
|
||||
|
||||
// Format changes the currency based on user preferences overriding the default
|
||||
// String() display
|
||||
func (p Pair) Format(delimiter string, uppercase bool) Pair {
|
||||
p.Delimiter = delimiter
|
||||
|
||||
if uppercase {
|
||||
return p.Upper()
|
||||
}
|
||||
return p.Lower()
|
||||
}
|
||||
|
||||
// Equal compares two currency pairs and returns whether or not they are equal
|
||||
func (p Pair) Equal(cPair Pair) bool {
|
||||
return strings.EqualFold(p.Base.String(), cPair.Base.String()) &&
|
||||
strings.EqualFold(p.Quote.String(), cPair.Quote.String())
|
||||
}
|
||||
|
||||
// EqualIncludeReciprocal compares two currency pairs and returns whether or not
|
||||
// they are the same including reciprocal currencies.
|
||||
func (p Pair) EqualIncludeReciprocal(cPair Pair) bool {
|
||||
if p.Base.Item == cPair.Base.Item &&
|
||||
p.Quote.Item == cPair.Quote.Item ||
|
||||
p.Base.Item == cPair.Quote.Item &&
|
||||
p.Quote.Item == cPair.Base.Item {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC
|
||||
func (p Pair) IsCryptoPair() bool {
|
||||
return storage.IsCryptocurrency(p.Base) &&
|
||||
storage.IsCryptocurrency(p.Quote)
|
||||
}
|
||||
|
||||
// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD
|
||||
func (p Pair) IsCryptoFiatPair() bool {
|
||||
return storage.IsCryptocurrency(p.Base) &&
|
||||
storage.IsFiatCurrency(p.Quote) ||
|
||||
storage.IsFiatCurrency(p.Base) &&
|
||||
storage.IsCryptocurrency(p.Quote)
|
||||
}
|
||||
|
||||
// IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD
|
||||
func (p Pair) IsFiatPair() bool {
|
||||
return storage.IsFiatCurrency(p.Base) && storage.IsFiatCurrency(p.Quote)
|
||||
}
|
||||
|
||||
// IsInvalid checks invalid pair if base and quote are the same
|
||||
func (p Pair) IsInvalid() bool {
|
||||
return p.Base.Item == p.Quote.Item
|
||||
}
|
||||
|
||||
// Swap turns the currency pair into its reciprocal
|
||||
func (p Pair) Swap() Pair {
|
||||
p.Base, p.Quote = p.Quote, p.Base
|
||||
return p
|
||||
}
|
||||
|
||||
// IsEmpty returns whether or not the pair is empty or is missing a currency
|
||||
// code
|
||||
func (p Pair) IsEmpty() bool {
|
||||
return p.Base.IsEmpty() || p.Quote.IsEmpty()
|
||||
}
|
||||
|
||||
// ContainsCurrency checks to see if a pair contains a specific currency
|
||||
func (p Pair) ContainsCurrency(c Code) bool {
|
||||
return p.Base.Item == c.Item || p.Quote.Item == c.Item
|
||||
}
|
||||
|
||||
// Pair holds currency pair information
|
||||
type Pair struct {
|
||||
Delimiter string `json:"delimiter"`
|
||||
Base Code `json:"base"`
|
||||
Quote Code `json:"quote"`
|
||||
}
|
||||
|
||||
117
currency/pair_methods.go
Normal file
117
currency/pair_methods.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package currency
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// String returns a currency pair string
|
||||
func (p Pair) String() string {
|
||||
return p.Base.String() + p.Delimiter + p.Quote.String()
|
||||
}
|
||||
|
||||
// Lower converts the pair object to lowercase
|
||||
func (p Pair) Lower() Pair {
|
||||
return Pair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.Lower(),
|
||||
Quote: p.Quote.Lower(),
|
||||
}
|
||||
}
|
||||
|
||||
// Upper converts the pair object to uppercase
|
||||
func (p Pair) Upper() Pair {
|
||||
return Pair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.Upper(),
|
||||
Quote: p.Quote.Upper(),
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON comforms type to the umarshaler interface
|
||||
func (p *Pair) UnmarshalJSON(d []byte) error {
|
||||
var pair string
|
||||
err := json.Unmarshal(d, &pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newPair, err := NewPairFromString(pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Base = newPair.Base
|
||||
p.Quote = newPair.Quote
|
||||
p.Delimiter = newPair.Delimiter
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON conforms type to the marshaler interface
|
||||
func (p Pair) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.String())
|
||||
}
|
||||
|
||||
// Format changes the currency based on user preferences overriding the default
|
||||
// String() display
|
||||
func (p Pair) Format(delimiter string, uppercase bool) Pair {
|
||||
newP := Pair{Base: p.Base, Quote: p.Quote, Delimiter: delimiter}
|
||||
if uppercase {
|
||||
return newP.Upper()
|
||||
}
|
||||
return newP.Lower()
|
||||
}
|
||||
|
||||
// Equal compares two currency pairs and returns whether or not they are equal
|
||||
func (p Pair) Equal(cPair Pair) bool {
|
||||
return strings.EqualFold(p.Base.String(), cPair.Base.String()) &&
|
||||
strings.EqualFold(p.Quote.String(), cPair.Quote.String())
|
||||
}
|
||||
|
||||
// EqualIncludeReciprocal compares two currency pairs and returns whether or not
|
||||
// they are the same including reciprocal currencies.
|
||||
func (p Pair) EqualIncludeReciprocal(cPair Pair) bool {
|
||||
if (p.Base.Item == cPair.Base.Item && p.Quote.Item == cPair.Quote.Item) ||
|
||||
(p.Base.Item == cPair.Quote.Item && p.Quote.Item == cPair.Base.Item) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC
|
||||
func (p Pair) IsCryptoPair() bool {
|
||||
return storage.IsCryptocurrency(p.Base) &&
|
||||
storage.IsCryptocurrency(p.Quote)
|
||||
}
|
||||
|
||||
// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD
|
||||
func (p Pair) IsCryptoFiatPair() bool {
|
||||
return (storage.IsCryptocurrency(p.Base) && storage.IsFiatCurrency(p.Quote)) ||
|
||||
(storage.IsFiatCurrency(p.Base) && storage.IsCryptocurrency(p.Quote))
|
||||
}
|
||||
|
||||
// IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD
|
||||
func (p Pair) IsFiatPair() bool {
|
||||
return storage.IsFiatCurrency(p.Base) && storage.IsFiatCurrency(p.Quote)
|
||||
}
|
||||
|
||||
// IsInvalid checks invalid pair if base and quote are the same
|
||||
func (p Pair) IsInvalid() bool {
|
||||
return p.Base.Item == p.Quote.Item
|
||||
}
|
||||
|
||||
// Swap turns the currency pair into its reciprocal
|
||||
func (p Pair) Swap() Pair {
|
||||
return Pair{Base: p.Quote, Quote: p.Base}
|
||||
}
|
||||
|
||||
// IsEmpty returns whether or not the pair is empty or is missing a currency
|
||||
// code
|
||||
func (p Pair) IsEmpty() bool {
|
||||
return p.Base.IsEmpty() && p.Quote.IsEmpty()
|
||||
}
|
||||
|
||||
// ContainsCurrency checks to see if a pair contains a specific currency
|
||||
func (p Pair) ContainsCurrency(c Code) bool {
|
||||
return p.Base.Item == c.Item || p.Quote.Item == c.Item
|
||||
}
|
||||
@@ -12,21 +12,35 @@ const (
|
||||
|
||||
func TestLower(t *testing.T) {
|
||||
t.Parallel()
|
||||
pair := NewPairFromString(defaultPair)
|
||||
pair, err := NewPairFromString(defaultPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := pair.Lower()
|
||||
expected := NewPairFromString(defaultPair).Lower()
|
||||
if actual != expected {
|
||||
expected, err := NewPairFromString(defaultPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if actual.String() != expected.Lower().String() {
|
||||
t.Errorf("Lower(): %s was not equal to expected value: %s",
|
||||
actual, expected)
|
||||
actual,
|
||||
expected.Lower())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpper(t *testing.T) {
|
||||
t.Parallel()
|
||||
pair := NewPairFromString(defaultPair)
|
||||
pair, err := NewPairFromString(defaultPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := pair.Upper()
|
||||
expected := NewPairFromString(defaultPair)
|
||||
if actual != expected {
|
||||
expected, err := NewPairFromString(defaultPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actual.String() != expected.String() {
|
||||
t.Errorf("Upper(): %s was not equal to expected value: %s",
|
||||
actual, expected)
|
||||
}
|
||||
@@ -34,7 +48,10 @@ func TestUpper(t *testing.T) {
|
||||
|
||||
func TestPairUnmarshalJSON(t *testing.T) {
|
||||
var unmarshalHere Pair
|
||||
configPair := NewPairDelimiter("btc_usd", "_")
|
||||
configPair, err := NewPairDelimiter("btc_usd", "_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(configPair)
|
||||
if err != nil {
|
||||
@@ -59,9 +76,9 @@ func TestPairUnmarshalJSON(t *testing.T) {
|
||||
|
||||
func TestPairMarshalJSON(t *testing.T) {
|
||||
quickstruct := struct {
|
||||
Pair Pair `json:"superPair"`
|
||||
Pair *Pair `json:"superPair"`
|
||||
}{
|
||||
Pair{Base: BTC, Quote: USD, Delimiter: "-"},
|
||||
&Pair{Base: BTC, Quote: USD, Delimiter: "-"},
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(quickstruct)
|
||||
@@ -158,7 +175,14 @@ func TestPair(t *testing.T) {
|
||||
|
||||
func TestDisplay(t *testing.T) {
|
||||
t.Parallel()
|
||||
pair := NewPairDelimiter(defaultPairWDelimiter, "-")
|
||||
_, err := NewPairDelimiter(defaultPairWDelimiter, "wow")
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
pair, err := NewPairDelimiter(defaultPairWDelimiter, "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := pair.String()
|
||||
expected := defaultPairWDelimiter
|
||||
if actual != expected {
|
||||
@@ -319,7 +343,24 @@ func TestNewPairWithDelimiter(t *testing.T) {
|
||||
|
||||
func TestNewPairDelimiter(t *testing.T) {
|
||||
t.Parallel()
|
||||
pair := NewPairDelimiter(defaultPairWDelimiter, "-")
|
||||
_, err := NewPairDelimiter("", "")
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
_, err = NewPairDelimiter("BTC_USD", "wow")
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
|
||||
_, err = NewPairDelimiter("BTC_USD", " ")
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
|
||||
pair, err := NewPairDelimiter(defaultPairWDelimiter, "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := pair.String()
|
||||
expected := defaultPairWDelimiter
|
||||
if actual != expected {
|
||||
@@ -338,7 +379,10 @@ func TestNewPairDelimiter(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
pair = NewPairDelimiter("BTC-MOVE-0626", "-")
|
||||
pair, err = NewPairDelimiter("BTC-MOVE-0626", "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual = pair.String()
|
||||
expected = "BTC-MOVE-0626"
|
||||
if actual != expected {
|
||||
@@ -348,7 +392,10 @@ func TestNewPairDelimiter(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
pair = NewPairDelimiter("fBTC-USDT", "-")
|
||||
pair, err = NewPairDelimiter("fBTC-USDT", "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual = pair.String()
|
||||
expected = "fBTC-USDT"
|
||||
if actual != expected {
|
||||
@@ -404,7 +451,10 @@ func TestNewPairFromIndex(t *testing.T) {
|
||||
func TestNewPairFromString(t *testing.T) {
|
||||
t.Parallel()
|
||||
pairStr := defaultPairWDelimiter
|
||||
pair := NewPairFromString(pairStr)
|
||||
pair, err := NewPairFromString(pairStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := pair.String()
|
||||
expected := defaultPairWDelimiter
|
||||
if actual != expected {
|
||||
@@ -415,7 +465,10 @@ func TestNewPairFromString(t *testing.T) {
|
||||
}
|
||||
|
||||
pairStr = defaultPair
|
||||
pair = NewPairFromString(pairStr)
|
||||
pair, err = NewPairFromString(pairStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual = pair.String()
|
||||
expected = defaultPair
|
||||
if actual != expected {
|
||||
@@ -428,23 +481,45 @@ func TestNewPairFromString(t *testing.T) {
|
||||
|
||||
func TestNewPairFromFormattedPairs(t *testing.T) {
|
||||
t.Parallel()
|
||||
p1, err := NewPairDelimiter("BTC-USDT", "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p2, err := NewPairDelimiter("LTC-USD", "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pairs := Pairs{
|
||||
NewPairDelimiter("BTC-USDT", "-"),
|
||||
NewPairDelimiter("LTC-USD", "-"),
|
||||
p1,
|
||||
p2,
|
||||
}
|
||||
|
||||
p, err := NewPairFromFormattedPairs("BTCUSDT", pairs, PairFormat{
|
||||
Uppercase: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p := NewPairFromFormattedPairs("BTCUSDT", pairs, PairFormat{Uppercase: true})
|
||||
if p.String() != "BTC-USDT" {
|
||||
t.Error("TestNewPairFromFormattedPairs: Expected currency was not found")
|
||||
}
|
||||
|
||||
p = NewPairFromFormattedPairs("btcusdt", pairs, PairFormat{Uppercase: false})
|
||||
p, err = NewPairFromFormattedPairs("btcusdt", pairs, PairFormat{Uppercase: false})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if p.String() != "BTC-USDT" {
|
||||
t.Error("TestNewPairFromFormattedPairs: Expected currency was not found")
|
||||
}
|
||||
|
||||
// Now a wrong one, will default to NewPairFromString
|
||||
p = NewPairFromFormattedPairs("ethusdt", pairs, PairFormat{})
|
||||
p, err = NewPairFromFormattedPairs("ethusdt", pairs, PairFormat{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if p.String() != "ethusdt" && p.Base.String() != "eth" {
|
||||
t.Error("TestNewPairFromFormattedPairs: Expected currency was not found")
|
||||
}
|
||||
@@ -514,29 +589,43 @@ func TestCopyPairFormat(t *testing.T) {
|
||||
t.Error("TestCopyPairFormat: Expected pair was not found")
|
||||
}
|
||||
|
||||
result = CopyPairFormat(NewPair(ETH, USD), pairs, true)
|
||||
np := NewPair(ETH, USD)
|
||||
result = CopyPairFormat(np, pairs, true)
|
||||
if result.String() != "" {
|
||||
t.Error("TestCopyPairFormat: Unexpected non empty pair returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindPairDifferences(t *testing.T) {
|
||||
pairList := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"})
|
||||
pairList, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dash, err := NewPairsFromStrings([]string{"DASH-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test new pair update
|
||||
newPairs, removedPairs := pairList.FindDifferences(NewPairsFromStrings([]string{"DASH-USD"}))
|
||||
newPairs, removedPairs := pairList.FindDifferences(dash)
|
||||
if len(newPairs) != 1 && len(removedPairs) != 3 {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
emptyPairsList, err := NewPairsFromStrings([]string{""})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test that we don't allow empty strings for new pairs
|
||||
newPairs, removedPairs = pairList.FindDifferences(NewPairsFromStrings([]string{""}))
|
||||
newPairs, removedPairs = pairList.FindDifferences(emptyPairsList)
|
||||
if len(newPairs) != 0 && len(removedPairs) != 3 {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
// Test that we don't allow empty strings for new pairs
|
||||
newPairs, removedPairs = NewPairsFromStrings([]string{""}).FindDifferences(pairList)
|
||||
newPairs, removedPairs = emptyPairsList.FindDifferences(pairList)
|
||||
if len(newPairs) != 3 && len(removedPairs) != 0 {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
12
currency/pair_types.go
Normal file
12
currency/pair_types.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package currency
|
||||
|
||||
// Pair holds currency pair information
|
||||
type Pair struct {
|
||||
ID string `json:"id"`
|
||||
Delimiter string `json:"delimiter,omitempty"`
|
||||
Base Code `json:"base,omitempty"`
|
||||
Quote Code `json:"quote,omitempty"`
|
||||
}
|
||||
|
||||
// Pairs defines a list of pairs
|
||||
type Pairs []Pair
|
||||
@@ -10,16 +10,21 @@ import (
|
||||
|
||||
// NewPairsFromStrings takes in currency pair strings and returns a currency
|
||||
// pair list
|
||||
func NewPairsFromStrings(pairs []string) Pairs {
|
||||
var ps Pairs
|
||||
for _, p := range pairs {
|
||||
if p == "" {
|
||||
func NewPairsFromStrings(pairs []string) (Pairs, error) {
|
||||
var newPairs Pairs
|
||||
for i := range pairs {
|
||||
if pairs[i] == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ps = append(ps, NewPairFromString(p))
|
||||
newPair, err := NewPairFromString(pairs[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newPairs = append(newPairs, newPair)
|
||||
}
|
||||
return ps
|
||||
return newPairs, nil
|
||||
}
|
||||
|
||||
// Strings returns a slice of strings referring to each currency pair
|
||||
@@ -79,8 +84,13 @@ func (p *Pairs) UnmarshalJSON(d []byte) error {
|
||||
}
|
||||
|
||||
var allThePairs Pairs
|
||||
for _, data := range strings.Split(pairs, ",") {
|
||||
allThePairs = append(allThePairs, NewPairFromString(data))
|
||||
oldPairs := strings.Split(pairs, ",")
|
||||
for i := range oldPairs {
|
||||
pair, err := NewPairFromString(oldPairs[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allThePairs = append(allThePairs, pair)
|
||||
}
|
||||
|
||||
*p = allThePairs
|
||||
@@ -101,21 +111,16 @@ func (p Pairs) Upper() Pairs {
|
||||
return upper
|
||||
}
|
||||
|
||||
// Slice exposes the underlying type
|
||||
func (p Pairs) Slice() []Pair {
|
||||
return p
|
||||
}
|
||||
|
||||
// Contains checks to see if a specified pair exists inside a currency pair
|
||||
// array
|
||||
func (p Pairs) Contains(check Pair, exact bool) bool {
|
||||
for _, pair := range p.Slice() {
|
||||
for i := range p {
|
||||
if exact {
|
||||
if pair.Equal(check) {
|
||||
if p[i].Equal(check) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if pair.EqualIncludeReciprocal(check) {
|
||||
if p[i].EqualIncludeReciprocal(check) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -127,11 +132,11 @@ func (p Pairs) Contains(check Pair, exact bool) bool {
|
||||
// and removes it from the list of pairs
|
||||
func (p Pairs) RemovePairsByFilter(filter Code) Pairs {
|
||||
var pairs Pairs
|
||||
for _, pair := range p.Slice() {
|
||||
if pair.ContainsCurrency(filter) {
|
||||
for i := range p {
|
||||
if p[i].ContainsCurrency(filter) {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, pair)
|
||||
pairs = append(pairs, p[i])
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
@@ -167,12 +172,12 @@ func (p Pairs) FindDifferences(pairs Pairs) (newPairs, removedPairs Pairs) {
|
||||
newPairs = append(newPairs, pairs[x])
|
||||
}
|
||||
}
|
||||
for _, oldPair := range p {
|
||||
if oldPair.String() == "" {
|
||||
for x := range p {
|
||||
if p[x].String() == "" {
|
||||
continue
|
||||
}
|
||||
if !pairs.Contains(oldPair, true) {
|
||||
removedPairs = append(removedPairs, oldPair)
|
||||
if !pairs.Contains(p[x], true) {
|
||||
removedPairs = append(removedPairs, p[x])
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -188,6 +193,3 @@ func (p Pairs) GetRandomPair() Pair {
|
||||
|
||||
return p[rand.Intn(pairsLen)]
|
||||
}
|
||||
|
||||
// Pairs defines a list of pairs
|
||||
type Pairs []Pair
|
||||
|
||||
@@ -6,7 +6,10 @@ import (
|
||||
)
|
||||
|
||||
func TestPairsUpper(t *testing.T) {
|
||||
pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "BTC_USD,BTC_AUD,BTC_LTC"
|
||||
|
||||
if pairs.Upper().Join() != expected {
|
||||
@@ -16,7 +19,10 @@ func TestPairsUpper(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPairsString(t *testing.T) {
|
||||
pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{"btc_usd", "btc_aud", "btc_ltc"}
|
||||
|
||||
for i, p := range pairs {
|
||||
@@ -28,7 +34,10 @@ func TestPairsString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPairsJoin(t *testing.T) {
|
||||
pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "btc_usd,btc_aud,btc_ltc"
|
||||
|
||||
if pairs.Join() != expected {
|
||||
@@ -38,7 +47,10 @@ func TestPairsJoin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPairsFormat(t *testing.T) {
|
||||
pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := "BTC-USD,BTC-AUD,BTC-LTC"
|
||||
if pairs.Format("-", "", true).Join() != expected {
|
||||
@@ -57,7 +69,10 @@ func TestPairsFormat(t *testing.T) {
|
||||
expected, pairs.Format(":", "KRW", true).Join())
|
||||
}
|
||||
|
||||
pairs = NewPairsFromStrings([]string{"DASHKRW", "BTCKRW"})
|
||||
pairs, err = NewPairsFromStrings([]string{"DASHKRW", "BTCKRW"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected = "dash-krw,btc-krw"
|
||||
if pairs.Format("-", "KRW", false).Join() != expected {
|
||||
t.Errorf("Pairs Join() error expected %s but received %s",
|
||||
@@ -106,10 +121,15 @@ func TestPairsUnmarshalJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPairsMarshalJSON(t *testing.T) {
|
||||
pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
quickstruct := struct {
|
||||
Pairs Pairs `json:"soManyPairs"`
|
||||
}{
|
||||
Pairs: NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}),
|
||||
Pairs: pairs,
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(quickstruct)
|
||||
@@ -176,12 +196,21 @@ func TestContains(t *testing.T) {
|
||||
var pairs = Pairs{
|
||||
NewPair(BTC, USD),
|
||||
NewPair(LTC, USD),
|
||||
NewPair(USD, ZRX),
|
||||
}
|
||||
|
||||
if !pairs.Contains(NewPair(BTC, USD), true) {
|
||||
t.Errorf("TestContains: Expected pair was not found")
|
||||
}
|
||||
|
||||
if pairs.Contains(NewPair(USD, BTC), true) {
|
||||
t.Errorf("TestContains: Unexpected pair was found")
|
||||
}
|
||||
|
||||
if !pairs.Contains(NewPair(USD, BTC), false) {
|
||||
t.Errorf("TestContains: Expected pair was not found")
|
||||
}
|
||||
|
||||
if pairs.Contains(NewPair(ETH, USD), false) {
|
||||
t.Errorf("TestContains: Non-existent pair was found")
|
||||
}
|
||||
|
||||
@@ -23,8 +23,14 @@ func init() {
|
||||
func (s *Storage) SetDefaults() {
|
||||
s.defaultBaseCurrency = USD
|
||||
s.baseCurrency = s.defaultBaseCurrency
|
||||
s.SetDefaultFiatCurrencies(USD, AUD, EUR, CNY)
|
||||
s.SetDefaultCryptocurrencies(BTC, LTC, ETH, DOGE, DASH, XRP, XMR)
|
||||
err := s.SetDefaultFiatCurrencies(USD, AUD, EUR, CNY)
|
||||
if err != nil {
|
||||
log.Errorf(log.Global, "Currency Storage: Setting default fiat currencies error: %s", err)
|
||||
}
|
||||
err = s.SetDefaultCryptocurrencies(BTC, LTC, ETH, DOGE, DASH, XRP, XMR)
|
||||
if err != nil {
|
||||
log.Errorf(log.Global, "Currency Storage: Setting default cryptocurrencies error: %s", err)
|
||||
}
|
||||
s.SetupConversionRates()
|
||||
s.fiatExchangeMarkets = forexprovider.NewDefaultFXProvider()
|
||||
}
|
||||
@@ -33,8 +39,9 @@ func (s *Storage) SetDefaults() {
|
||||
// dump file and keep foreign exchange rates updated as fast as possible without
|
||||
// triggering rate limiters, it will also run a full cryptocurrency check
|
||||
// through coin market cap and expose analytics for exchange services
|
||||
func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration, filePath string, verbose bool) error {
|
||||
func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration, filePath string) error {
|
||||
s.mtx.Lock()
|
||||
s.shutdown = make(chan struct{})
|
||||
|
||||
if !settings.Cryptocurrencies.HasData() {
|
||||
s.mtx.Unlock()
|
||||
@@ -48,17 +55,17 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration
|
||||
}
|
||||
s.baseCurrency = settings.FiatDisplayCurrency
|
||||
log.Debugf(log.Global,
|
||||
"Fiat display currency: %s.\n", s.baseCurrency)
|
||||
"Fiat display currency: %s.\n",
|
||||
s.baseCurrency)
|
||||
|
||||
if settings.CryptocurrencyProvider.Enabled {
|
||||
log.Debugln(
|
||||
log.Global,
|
||||
log.Debugln(log.Global,
|
||||
"Setting up currency analysis system with Coinmarketcap...")
|
||||
c := &coinmarketcap.Coinmarketcap{}
|
||||
c.SetDefaults()
|
||||
err := c.Setup(coinmarketcap.Settings{
|
||||
Name: settings.CryptocurrencyProvider.Name,
|
||||
Enabled: true,
|
||||
Enabled: settings.CryptocurrencyProvider.Enabled,
|
||||
AccountPlan: settings.CryptocurrencyProvider.AccountPlan,
|
||||
APIkey: settings.CryptocurrencyProvider.APIkey,
|
||||
Verbose: settings.CryptocurrencyProvider.Verbose,
|
||||
@@ -174,16 +181,34 @@ func (s *Storage) SetupConversionRates() {
|
||||
|
||||
// SetDefaultFiatCurrencies assigns the default fiat currency list and adds it
|
||||
// to the running list
|
||||
func (s *Storage) SetDefaultFiatCurrencies(c ...Code) {
|
||||
func (s *Storage) SetDefaultFiatCurrencies(c ...Code) error {
|
||||
for i := range c {
|
||||
err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Fiat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.defaultFiatCurrencies = append(s.defaultFiatCurrencies, c...)
|
||||
s.fiatCurrencies = append(s.fiatCurrencies, c...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDefaultCryptocurrencies assigns the default cryptocurrency list and adds
|
||||
// it to the running list
|
||||
func (s *Storage) SetDefaultCryptocurrencies(c ...Code) {
|
||||
func (s *Storage) SetDefaultCryptocurrencies(c ...Code) error {
|
||||
for i := range c {
|
||||
err := s.currencyCodes.UpdateCurrency("",
|
||||
c[i].String(),
|
||||
"",
|
||||
0,
|
||||
Cryptocurrency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.defaultCryptoCurrencies = append(s.defaultCryptoCurrencies, c...)
|
||||
s.cryptocurrencies = append(s.cryptocurrencies, c...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupForexProviders sets up a new instance of the forex providers
|
||||
@@ -228,7 +253,7 @@ func (s *Storage) ForeignExchangeUpdater() {
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.shutdownC:
|
||||
case <-s.shutdown:
|
||||
return
|
||||
|
||||
case <-SeedForeignExchangeTick.C:
|
||||
@@ -248,36 +273,29 @@ func (s *Storage) ForeignExchangeUpdater() {
|
||||
|
||||
// SeedCurrencyAnalysisData sets a new instance of a coinmarketcap data.
|
||||
func (s *Storage) SeedCurrencyAnalysisData() error {
|
||||
b, err := ioutil.ReadFile(s.path)
|
||||
if err != nil {
|
||||
err = s.FetchCurrencyAnalysisData()
|
||||
if s.currencyCodes.LastMainUpdate.IsZero() {
|
||||
b, err := ioutil.ReadFile(s.path)
|
||||
if err != nil {
|
||||
return s.WriteCurrencyDataToFile(s.path, false)
|
||||
return s.FetchCurrencyAnalysisData()
|
||||
}
|
||||
var f *File
|
||||
err = json.Unmarshal(b, &f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.LoadFileCurrencyData(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.WriteCurrencyDataToFile(s.path, true)
|
||||
}
|
||||
|
||||
var fromFile File
|
||||
err = json.Unmarshal(b, &fromFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.LoadFileCurrencyData(&fromFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Based on update delay update the file
|
||||
if fromFile.LastMainUpdate.After(fromFile.LastMainUpdate.Add(s.currencyFileUpdateDelay)) ||
|
||||
fromFile.LastMainUpdate.IsZero() {
|
||||
err = s.FetchCurrencyAnalysisData()
|
||||
if time.Now().After(s.currencyCodes.LastMainUpdate.Add(s.currencyFileUpdateDelay)) ||
|
||||
s.currencyCodes.LastMainUpdate.IsZero() {
|
||||
err := s.FetchCurrencyAnalysisData()
|
||||
if err != nil {
|
||||
return s.WriteCurrencyDataToFile(s.path, false)
|
||||
return err
|
||||
}
|
||||
|
||||
return s.WriteCurrencyDataToFile(s.path, true)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -304,7 +322,7 @@ func (s *Storage) WriteCurrencyDataToFile(path string, mainUpdate bool) error {
|
||||
|
||||
if mainUpdate {
|
||||
t := time.Now()
|
||||
data.LastMainUpdate = t
|
||||
data.LastMainUpdate = t.Unix()
|
||||
s.currencyCodes.LastMainUpdate = t
|
||||
}
|
||||
|
||||
@@ -320,41 +338,62 @@ func (s *Storage) WriteCurrencyDataToFile(path string, mainUpdate bool) error {
|
||||
// LoadFileCurrencyData loads currencies into the currency codes
|
||||
func (s *Storage) LoadFileCurrencyData(f *File) error {
|
||||
for i := range f.Contracts {
|
||||
err := s.currencyCodes.LoadItem(&f.Contracts[i])
|
||||
contract := f.Contracts[i]
|
||||
contract.Role = Contract
|
||||
err := s.currencyCodes.LoadItem(&contract)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := range f.Cryptocurrency {
|
||||
err := s.currencyCodes.LoadItem(&f.Cryptocurrency[i])
|
||||
crypto := f.Cryptocurrency[i]
|
||||
crypto.Role = Cryptocurrency
|
||||
err := s.currencyCodes.LoadItem(&crypto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := range f.Token {
|
||||
err := s.currencyCodes.LoadItem(&f.Token[i])
|
||||
token := f.Token[i]
|
||||
token.Role = Token
|
||||
err := s.currencyCodes.LoadItem(&token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := range f.FiatCurrency {
|
||||
err := s.currencyCodes.LoadItem(&f.FiatCurrency[i])
|
||||
fiat := f.FiatCurrency[i]
|
||||
fiat.Role = Fiat
|
||||
err := s.currencyCodes.LoadItem(&fiat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := range f.UnsetCurrency {
|
||||
err := s.currencyCodes.LoadItem(&f.UnsetCurrency[i])
|
||||
unset := f.UnsetCurrency[i]
|
||||
unset.Role = Unset
|
||||
err := s.currencyCodes.LoadItem(&unset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.currencyCodes.LastMainUpdate = f.LastMainUpdate
|
||||
switch t := f.LastMainUpdate.(type) {
|
||||
case string:
|
||||
parseT, err := time.Parse(time.RFC3339Nano, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.currencyCodes.LastMainUpdate = parseT
|
||||
case float64:
|
||||
s.currencyCodes.LastMainUpdate = time.Unix(int64(t), 0)
|
||||
default:
|
||||
return errors.New("unhandled type conversion for LastMainUpdate time")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -372,19 +411,22 @@ func (s *Storage) UpdateCurrencies() error {
|
||||
}
|
||||
|
||||
if m[x].Platform.Symbol != "" {
|
||||
err := s.currencyCodes.UpdateToken(m[x].Name,
|
||||
err = s.currencyCodes.UpdateCurrency(m[x].Name,
|
||||
m[x].Symbol,
|
||||
m[x].Platform.Symbol,
|
||||
m[x].ID)
|
||||
m[x].ID,
|
||||
Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err := s.currencyCodes.UpdateCryptocurrency(m[x].Name,
|
||||
err = s.currencyCodes.UpdateCurrency(m[x].Name,
|
||||
m[x].Symbol,
|
||||
m[x].ID)
|
||||
"",
|
||||
m[x].ID,
|
||||
Cryptocurrency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -452,8 +494,7 @@ func (s *Storage) GetExchangeRates() (Conversions, error) {
|
||||
func (s *Storage) SeedForeignExchangeRates() error {
|
||||
s.fxRates.mtx.Lock()
|
||||
defer s.fxRates.mtx.Unlock()
|
||||
rates, err := s.fiatExchangeMarkets.GetCurrencyData(
|
||||
s.baseCurrency.String(),
|
||||
rates, err := s.fiatExchangeMarkets.GetCurrencyData(s.baseCurrency.String(),
|
||||
s.fiatCurrencies.Strings())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -463,15 +504,7 @@ func (s *Storage) SeedForeignExchangeRates() error {
|
||||
|
||||
// UpdateForeignExchangeRates sets exchange rates on the FX map
|
||||
func (s *Storage) updateExchangeRates(m map[string]float64) error {
|
||||
err := s.fxRates.Update(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.path != "" {
|
||||
return s.WriteCurrencyDataToFile(s.path, false)
|
||||
}
|
||||
return nil
|
||||
return s.fxRates.Update(m)
|
||||
}
|
||||
|
||||
// SetupCryptoProvider sets congiguration parameters and starts a new instance
|
||||
@@ -502,9 +535,9 @@ func (s *Storage) GetTotalMarketCryptocurrencies() (Currencies, error) {
|
||||
|
||||
// IsDefaultCurrency returns if a currency is a default currency
|
||||
func (s *Storage) IsDefaultCurrency(c Code) bool {
|
||||
t, _ := GetTranslation(c)
|
||||
for i := range s.defaultFiatCurrencies {
|
||||
if s.defaultFiatCurrencies[i].Match(c) || s.defaultFiatCurrencies[i].Match(t) {
|
||||
if s.defaultFiatCurrencies[i].Match(c) ||
|
||||
s.defaultFiatCurrencies[i].Match(GetTranslation(c)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -514,9 +547,9 @@ func (s *Storage) IsDefaultCurrency(c Code) bool {
|
||||
// IsDefaultCryptocurrency returns if a cryptocurrency is a default
|
||||
// cryptocurrency
|
||||
func (s *Storage) IsDefaultCryptocurrency(c Code) bool {
|
||||
t, _ := GetTranslation(c)
|
||||
for _, d := range s.defaultCryptoCurrencies {
|
||||
if d.Match(c) || d.Match(t) {
|
||||
for i := range s.defaultCryptoCurrencies {
|
||||
if s.defaultCryptoCurrencies[i].Match(c) ||
|
||||
s.defaultCryptoCurrencies[i].Match(GetTranslation(c)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -534,9 +567,9 @@ func (s *Storage) IsFiatCurrency(c Code) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
t, _ := GetTranslation(c)
|
||||
for _, d := range s.fiatCurrencies {
|
||||
if d.Match(c) || d.Match(t) {
|
||||
for i := range s.fiatCurrencies {
|
||||
if s.fiatCurrencies[i].Match(c) ||
|
||||
s.fiatCurrencies[i].Match(GetTranslation(c)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -555,9 +588,9 @@ func (s *Storage) IsCryptocurrency(c Code) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
t, _ := GetTranslation(c)
|
||||
for _, d := range s.cryptocurrencies {
|
||||
if d.Match(c) || d.Match(t) {
|
||||
for i := range s.cryptocurrencies {
|
||||
if s.cryptocurrencies[i].Match(c) ||
|
||||
s.cryptocurrencies[i].Match(GetTranslation(c)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -573,15 +606,12 @@ func (s *Storage) ValidateCode(newCode string) Code {
|
||||
|
||||
// ValidateFiatCode validates a fiat currency string and returns a currency
|
||||
// code
|
||||
func (s *Storage) ValidateFiatCode(newCode string) (Code, error) {
|
||||
c, err := s.currencyCodes.RegisterFiat(newCode)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
func (s *Storage) ValidateFiatCode(newCode string) Code {
|
||||
c := s.currencyCodes.RegisterFiat(newCode)
|
||||
if !s.fiatCurrencies.Contains(c) {
|
||||
s.fiatCurrencies = append(s.fiatCurrencies, c)
|
||||
}
|
||||
return c, nil
|
||||
return c
|
||||
}
|
||||
|
||||
// ValidateCryptoCode validates a cryptocurrency string and returns a currency
|
||||
@@ -637,9 +667,9 @@ func (s *Storage) GetBaseCurrency() Code {
|
||||
// UpdateEnabledCryptoCurrencies appends new cryptocurrencies to the enabled
|
||||
// currency list
|
||||
func (s *Storage) UpdateEnabledCryptoCurrencies(c Currencies) {
|
||||
for _, i := range c {
|
||||
if !s.cryptocurrencies.Contains(i) {
|
||||
s.cryptocurrencies = append(s.cryptocurrencies, i)
|
||||
for i := range c {
|
||||
if !s.cryptocurrencies.Contains(c[i]) {
|
||||
s.cryptocurrencies = append(s.cryptocurrencies, c[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -647,9 +677,10 @@ func (s *Storage) UpdateEnabledCryptoCurrencies(c Currencies) {
|
||||
// UpdateEnabledFiatCurrencies appends new fiat currencies to the enabled
|
||||
// currency list
|
||||
func (s *Storage) UpdateEnabledFiatCurrencies(c Currencies) {
|
||||
for _, i := range c {
|
||||
if !s.fiatCurrencies.Contains(i) && !s.cryptocurrencies.Contains(i) {
|
||||
s.fiatCurrencies = append(s.fiatCurrencies, i)
|
||||
for i := range c {
|
||||
if !s.fiatCurrencies.Contains(c[i]) &&
|
||||
!s.cryptocurrencies.Contains(c[i]) {
|
||||
s.fiatCurrencies = append(s.fiatCurrencies, c[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -709,3 +740,10 @@ func (s *Storage) NewConversion(from, to Code) (Conversion, error) {
|
||||
func (s *Storage) IsVerbose() bool {
|
||||
return s.Verbose
|
||||
}
|
||||
|
||||
// Shutdown shuts down the currency storage system and saves to currency.json
|
||||
func (s *Storage) Shutdown() error {
|
||||
close(s.shutdown)
|
||||
s.wg.Wait()
|
||||
return s.WriteCurrencyDataToFile(s.path, true)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ func TestRunUpdater(t *testing.T) {
|
||||
var newStorage Storage
|
||||
|
||||
emptyMainConfig := MainConfiguration{}
|
||||
err := newStorage.RunUpdater(BotOverrides{}, &emptyMainConfig, "", false)
|
||||
err := newStorage.RunUpdater(BotOverrides{}, &emptyMainConfig, "")
|
||||
if err == nil {
|
||||
t.Fatal("storage RunUpdater() error cannot be nil")
|
||||
}
|
||||
@@ -16,12 +16,12 @@ func TestRunUpdater(t *testing.T) {
|
||||
FiatDisplayCurrency: USD,
|
||||
}
|
||||
|
||||
err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "", false)
|
||||
err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "")
|
||||
if err == nil {
|
||||
t.Fatal("storage RunUpdater() error cannot be nil")
|
||||
}
|
||||
|
||||
err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "/bla", false)
|
||||
err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "/bla")
|
||||
if err != nil {
|
||||
t.Fatal("storage RunUpdater() error", err)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ type Storage struct {
|
||||
foreignExchangeUpdateDelay time.Duration
|
||||
mtx sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
shutdownC chan struct{}
|
||||
shutdown chan struct{}
|
||||
updaterRunning bool
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package currency
|
||||
|
||||
// GetTranslation returns similar strings for a particular currency if not found
|
||||
// returns the code back
|
||||
func GetTranslation(currency Code) (Code, bool) {
|
||||
func GetTranslation(currency Code) Code {
|
||||
val, ok := translations[currency]
|
||||
if !ok {
|
||||
return currency, ok
|
||||
return currency
|
||||
}
|
||||
return val, ok
|
||||
return val
|
||||
}
|
||||
|
||||
var translations = map[Code]Code{
|
||||
|
||||
@@ -5,29 +5,21 @@ import "testing"
|
||||
func TestGetTranslation(t *testing.T) {
|
||||
currencyPair := NewPair(BTC, USD)
|
||||
expected := XBT
|
||||
actual, ok := GetTranslation(currencyPair.Base)
|
||||
if !ok {
|
||||
t.Error("GetTranslation: failed to retrieve translation for BTC")
|
||||
}
|
||||
|
||||
actual := GetTranslation(currencyPair.Base)
|
||||
if expected != actual {
|
||||
t.Error("GetTranslation: translation result was different to expected result")
|
||||
}
|
||||
|
||||
currencyPair.Base = NEO
|
||||
_, ok = GetTranslation(currencyPair.Base)
|
||||
if ok {
|
||||
actual = GetTranslation(currencyPair.Base)
|
||||
if actual != currencyPair.Base {
|
||||
t.Error("GetTranslation: no error on non translatable currency")
|
||||
}
|
||||
|
||||
expected = BTC
|
||||
currencyPair.Base = XBT
|
||||
|
||||
actual, ok = GetTranslation(currencyPair.Base)
|
||||
if !ok {
|
||||
t.Error("GetTranslation: failed to retrieve translation for BTC")
|
||||
}
|
||||
|
||||
actual = GetTranslation(currencyPair.Base)
|
||||
if expected != actual {
|
||||
t.Error("GetTranslation: translation result was different to expected result")
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ Find out which asset types are supported by the exchange and add them to the pai
|
||||
config.GetDefaultFilePath()
|
||||
```
|
||||
|
||||
```go
|
||||
```js
|
||||
{
|
||||
"name": "FTX",
|
||||
"enabled": true,
|
||||
@@ -61,37 +61,35 @@ config.GetDefaultFilePath()
|
||||
"websocketOrderbookBufferLimit": 5,
|
||||
"baseCurrencies": "USD",
|
||||
"currencyPairs": {
|
||||
"assetTypes": [
|
||||
"spot",
|
||||
"futures"
|
||||
],
|
||||
"pairs": {
|
||||
"futures": {
|
||||
"enabled": "BTC-PERP",
|
||||
"available": "BTC-PERP",
|
||||
"requestFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "-"
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "-"
|
||||
}
|
||||
"pairs": {
|
||||
"futures": {
|
||||
"assetEnabled": true,
|
||||
"enabled": "BTC-PERP",
|
||||
"available": "BTC-PERP",
|
||||
"requestFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "-"
|
||||
},
|
||||
"spot": {
|
||||
"enabled": "BTC/USD",
|
||||
"available": "BTC/USD",
|
||||
"requestFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "/"
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "/"
|
||||
}
|
||||
"configFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "-"
|
||||
}
|
||||
},
|
||||
"spot": {
|
||||
"assetEnabled": true,
|
||||
"enabled": "BTC/USD",
|
||||
"available": "BTC/USD",
|
||||
"requestFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "/"
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "/"
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"authenticatedSupport": false,
|
||||
"authenticatedWebsocketApiSupport": false,
|
||||
@@ -177,6 +175,16 @@ Similar to the configs, spot support is inbuilt but other asset types will need
|
||||
Delimiter: "-",
|
||||
},
|
||||
}
|
||||
|
||||
err := f.StoreAssetPairFormat(asset.Spot, spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
err = f.StoreAssetPairFormat(asset.Futures, futures)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
```
|
||||
|
||||
### Document the addition of the new exchange (FTX exchange is used as an example below):
|
||||
@@ -645,16 +653,6 @@ The currency package contains many helper functions to format and process curren
|
||||
|
||||
### Websocket addition if exchange supports it:
|
||||
|
||||
#### Add websocket to exchange struct in ftx.go
|
||||
|
||||
```go
|
||||
// FTX is the overarching type across this package
|
||||
type FTX struct {
|
||||
exchange.Base
|
||||
WebsocketConn *wshandler.WebsocketConnection // Add this line
|
||||
}
|
||||
```
|
||||
|
||||
#### Websocket Setup:
|
||||
|
||||
- Set the websocket url in ftx_websocket.go that is provided in the documentation:
|
||||
@@ -672,27 +670,35 @@ func (f *FTX) WsConnect() error {
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := f.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
err := f.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{
|
||||
// Can set up custom ping handler per websocket connection.
|
||||
f.Websocket.Conn.SetupPingHandler(wshandler.WebsocketPingHandler{
|
||||
MessageType: websocket.PingMessage,
|
||||
Delay: ftxWebsocketTimer,
|
||||
})
|
||||
if f.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", f.Name)
|
||||
}
|
||||
// This reader routine is called prior to initiating a subscription for
|
||||
// efficient processing.
|
||||
go f.wsReadData()
|
||||
if f.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
err := f.WsAuth()
|
||||
err = f.WsAuth()
|
||||
if err != nil {
|
||||
f.Websocket.DataHandler <- err
|
||||
f.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
}
|
||||
f.GenerateDefaultSubscriptions()
|
||||
return nil
|
||||
// Generates the default subscription set, based off enabled pairs.
|
||||
subs, err := f.GenerateDefaultSubscriptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Finally subscribes to each individual channel.
|
||||
return f.Websocket.SubscribeToChannels(subs)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -700,22 +706,44 @@ func (f *FTX) WsConnect() error {
|
||||
|
||||
```go
|
||||
// GenerateDefaultSubscriptions generates default subscription
|
||||
func (f *FTX) GenerateDefaultSubscriptions() {
|
||||
var channels = []string{wsTicker, wsTrades, wsOrderbook, wsMarkets, wsFills, wsOrders}
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
for a := range f.CurrencyPairs.AssetTypes {
|
||||
pairs := f.GetEnabledPairs(f.CurrencyPairs.AssetTypes[a])
|
||||
func (f *FTX) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: wsMarkets,
|
||||
})
|
||||
// Ranges over available channels, pairs and asset types to produce a full
|
||||
// subscription list.
|
||||
var channels = []string{wsTicker, wsTrades, wsOrderbook}
|
||||
assets := f.GetAssetTypes()
|
||||
for a := range assets {
|
||||
pairs, err := f.GetEnabledPairs(assets[a])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for z := range pairs {
|
||||
newPair := currency.NewPairWithDelimiter(pairs[z].Base.String(), pairs[z].Quote.String(), "-")
|
||||
newPair := currency.NewPairWithDelimiter(pairs[z].Base.String(),
|
||||
pairs[z].Quote.String(),
|
||||
"-")
|
||||
for x := range channels {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: newPair,
|
||||
})
|
||||
subscriptions = append(subscriptions,
|
||||
stream.ChannelSubscription{
|
||||
Channel: channels[x],
|
||||
Currency: newPair,
|
||||
Asset: assets[a],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
f.Websocket.SubscribeToChannels(subscriptions)
|
||||
// Appends authenticated channels to the subscription list
|
||||
if f.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
var authchan = []string{wsOrders, wsFills}
|
||||
for x := range authchan {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: authchan[x],
|
||||
})
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
```
|
||||
|
||||
@@ -753,22 +781,47 @@ type WsSub struct {
|
||||
|
||||
```go
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (f *FTX) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
var sub WsSub
|
||||
a, err := f.GetPairAssetType(channelToSubscribe.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
func (f *FTX) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
// For subscriptions we try to batch as much as possible to limit the amount
|
||||
// of connection usage but sometimes this is not supported on the exchange
|
||||
// API.
|
||||
var errs common.Errors // This is an array of errors useful in the event that one channel subscription errors but we can subscribe to the next iteration.
|
||||
channels:
|
||||
for i := range channelsToSubscribe {
|
||||
// Type we declared above to send via our websocket connection.
|
||||
var sub WsSub
|
||||
sub.Channel = channelsToSubscribe[i].Channel
|
||||
sub.Operation = subscribe
|
||||
|
||||
switch channelsToSubscribe[i].Channel {
|
||||
case wsFills, wsOrders, wsMarkets:
|
||||
// Authenticated wsFills && wsOrders or wsMarkets which is a channel subscription for the full set of tradable markets do not need a currency pair association.
|
||||
default:
|
||||
a, err := f.GetPairAssetType(channelsToSubscribe[i].Currency)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue channels
|
||||
}
|
||||
// Ensures our outbound currency pair is formatted correctly, sometimes our configuration format is different from what our request format needs to be.
|
||||
formattedPair, err := f.FormatExchangeCurrency(channelsToSubscribe[i].Currency, a)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue channels
|
||||
}
|
||||
sub.Market = formattedPair.String()
|
||||
}
|
||||
err := f.Websocket.Conn.SendJSONMessage(sub)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
// When we have a successful subscription, we can alert our internal management system of the success.
|
||||
f.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
|
||||
}
|
||||
switch channelToSubscribe.Channel {
|
||||
case wsFills, wsOrders:
|
||||
sub.Operation = "subscribe"
|
||||
sub.Channel = channelToSubscribe.Channel
|
||||
default:
|
||||
sub.Operation = "subscribe"
|
||||
sub.Channel = channelToSubscribe.Channel
|
||||
sub.Market = f.FormatExchangeCurrency(channelToSubscribe.Currency, a).String()
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
return f.WebsocketConn.SendJSONMessage(sub)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
@@ -782,7 +835,7 @@ Run gocryptotrader with the following settings enabled in config
|
||||
},
|
||||
"enabled": {
|
||||
"autoPairUpdates": true,
|
||||
"websocketAPI": true
|
||||
"websocketAPI": true // <- Change this to true if it is false
|
||||
```
|
||||
|
||||
#### Handle websocket data:
|
||||
@@ -800,13 +853,12 @@ func (f *FTX) wsReadData() {
|
||||
case <-f.Websocket.ShutdownC:
|
||||
return
|
||||
default:
|
||||
resp, err := f.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
f.Websocket.ReadMessageErrors <- err
|
||||
resp := f.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
}
|
||||
f.Websocket.TrafficAlert <- struct{}{}
|
||||
err = f.wsHandleData(resp.Raw)
|
||||
|
||||
err := f.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
f.Websocket.DataHandler <- err
|
||||
}
|
||||
@@ -963,7 +1015,7 @@ func (f *FTX) WsAuth() error {
|
||||
Time: intNonce,
|
||||
},
|
||||
}
|
||||
return f.WebsocketConn.SendJSONMessage(req)
|
||||
return f.Websocket.Conn.SendJSONMessage(req)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -971,16 +1023,42 @@ func (f *FTX) WsAuth() error {
|
||||
|
||||
```go
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (f *FTX) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
var unSub WsSub
|
||||
a, err := f.GetPairAssetType(channelToSubscribe.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
func (f *FTX) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
// As with subscribing we want to batch as much as possible, but sometimes this cannot be achieved due to API shortfalls.
|
||||
var errs common.Errors
|
||||
channels:
|
||||
for i := range channelsToUnsubscribe {
|
||||
var unSub WsSub
|
||||
unSub.Operation = unsubscribe
|
||||
unSub.Channel = channelsToUnsubscribe[i].Channel
|
||||
switch channelsToUnsubscribe[i].Channel {
|
||||
case wsFills, wsOrders, wsMarkets:
|
||||
default:
|
||||
a, err := f.GetPairAssetType(channelsToUnsubscribe[i].Currency)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue channels
|
||||
}
|
||||
|
||||
formattedPair, err := f.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, a)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue channels
|
||||
}
|
||||
unSub.Market = formattedPair.String()
|
||||
}
|
||||
err := f.Websocket.Conn.SendJSONMessage(unSub)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
// When we have a successful unsubscription, we can alert our internal management system of the success.
|
||||
f.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
|
||||
}
|
||||
unSub.Operation = "unsubscribe"
|
||||
unSub.Channel = channelToSubscribe.Channel
|
||||
unSub.Market = f.FormatExchangeCurrency(channelToSubscribe.Currency, a).String()
|
||||
return f.WebsocketConn.SendJSONMessage(unSub)
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
@@ -989,24 +1067,53 @@ func (f *FTX) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscript
|
||||
Add websocket functionality if supported to Setup:
|
||||
|
||||
```go
|
||||
err = f.Websocket.Setup(
|
||||
&wshandler.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: ftxWSURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: f.WsConnect,
|
||||
Subscriber: f.Subscribe,
|
||||
UnSubscriber: f.Unsubscribe,
|
||||
Features: &f.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
// Setup takes in the supplied exchange configuration details and sets params
|
||||
func (f *FTX) Setup(exch *config.ExchangeConfig) error {
|
||||
if !exch.Enabled {
|
||||
f.SetEnabled(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := f.SetupDefaults(exch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
}
|
||||
|
||||
// Websocket details setup below
|
||||
err = f.Websocket.Setup(&stream.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: ftxWSURL, // Default ws endpoint so we can roll back via CLI if needed.
|
||||
ExchangeName: exch.Name, // Sets websocket name to the exchange name.
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: f.WsConnect, // Connector function outlined above.
|
||||
Subscriber: f.Subscribe, // Subscriber function outlined above.
|
||||
UnSubscriber: f.Unsubscribe, // Unsubscriber function outlined above.
|
||||
GenerateSubscriptions: f.GenerateDefaultSubscriptions, // GenerateDefaultSubscriptions function outlined above.
|
||||
Features: &f.Features.Supports.WebsocketCapabilities, // Defines the capabilities of the websocket outlined in supported features struct. This allows the websocket connection to be flushed appropriately if we have a pair/asset enable/disable change. This is outlined below.
|
||||
|
||||
// Orderbook buffer specific variables for processing orderbook updates via websocket feed.
|
||||
OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit,
|
||||
// Other orderbook buffer vars:
|
||||
// BufferEnabled bool
|
||||
// SortBuffer bool
|
||||
// SortBufferByUpdateIDs bool
|
||||
// UpdateEntriesByID bool
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Sets up a new connection for the websocket, there are two separate connections denoted by the ConnectionSetup struct auth bool.
|
||||
return f.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
// RateLimit int64 rudimentary rate limit that sleeps connection in milliseconds before sending designated payload
|
||||
// Authenticated bool sets if the connection is dedicated for an authenticated websocket stream which can be accessed from the Websocket field variable AuthConn e.g. f.Websocket.AuthConn
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Below are the features supported by FTX API protocol:
|
||||
|
||||
@@ -1053,32 +1160,35 @@ Below are the features supported by FTX API protocol:
|
||||
Initially the functions return nil or common.ErrNotYetImplemented
|
||||
|
||||
```go
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (f *FTX) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return f.Websocket, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (f *FTX) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
f.Websocket.SubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (f *FTX) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
f.Websocket.RemoveSubscribedChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (f *FTX) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return f.Websocket.GetSubscriptions(), nil
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (f *FTX) AuthenticateWebsocket() error {
|
||||
return f.WsAuth()
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
## Last but not least - Live testing
|
||||
|
||||
### Live testing websocket via [gctcli](../cmd/gctcli/main.go)
|
||||
|
||||
Please test all `websocket` commands below whilst a GoCryptoTrader instance is running and with the exchange websocket setting enabled:
|
||||
|
||||
- `getinfo` to ensure fetching websocket information is possible (that the websocket connection is enabled, connected and is running).
|
||||
- `disable/enable` to ensure disabling/enabling a websocket connection disconnects/connects accordingly.
|
||||
- `getsubs` to ensure the subscriptions are in sync with the exchange's config settings or by manual subscriptions added/removed via `gctcli`.
|
||||
- `setproxy` to ensure that a proxy can be set and resets the websocket connection accordingly.
|
||||
- `seturl` to ensure that a new websocket URL can be set in the event of an API endpoint change whilst an instance of GoCryptoTrader is already running.
|
||||
|
||||
Please test all `pair` commands to disable and enable different assets types to witness subscriptions and unsubscriptions:
|
||||
|
||||
- `get` to ensure correct enabled and disabled pairs for a supported asset type.
|
||||
- `disableasset` to ensure disabling of entire asset class and associated unsubscriptions.
|
||||
- `enableasset` to ensure correct enabling of entire asset class and associated subscriptions.
|
||||
- `disable` to ensure correct disabling of pair(s) and and associated unsubscriptions.
|
||||
- `enable` to ensure correct enabling of pair(s) and associated subscriptions.
|
||||
- `enableall` to ensure correct enabling of all pairs for an asset type and associated subscriptions.
|
||||
- `disableall` to ensure correct disabling of all pairs for an asset type and associated unsubscriptions.
|
||||
|
||||
## Open a PR
|
||||
|
||||
Submitting a PR is easy and all are welcome additions to the public repository. Submit via github.com/thrasher-corp/gocryptotrader or contact our team via slack for more information.
|
||||
@@ -385,8 +385,7 @@ func (e *Engine) Start() error {
|
||||
CurrencyDelay: e.Config.Currency.CurrencyFileUpdateDuration,
|
||||
FxRateDelay: e.Config.Currency.ForeignExchangeUpdateDuration,
|
||||
},
|
||||
e.Settings.DataDir,
|
||||
e.Settings.Verbose)
|
||||
e.Settings.DataDir)
|
||||
if err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Currency updater system failed to start %v", err)
|
||||
}
|
||||
@@ -514,6 +513,10 @@ func (e *Engine) Stop() {
|
||||
}
|
||||
}
|
||||
|
||||
if err := currency.ShutdownStorageUpdater(); err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Currency storage system. Error: %v", err)
|
||||
}
|
||||
|
||||
if !e.Settings.EnableDryRun {
|
||||
err := e.Config.SaveConfig(e.Settings.ConfigFile, false)
|
||||
if err != nil {
|
||||
|
||||
@@ -72,7 +72,7 @@ var Events []*Event
|
||||
|
||||
// Add adds an event to the Events chain and returns an index/eventID
|
||||
// and an error
|
||||
func Add(exchange, item string, condition EventConditionParams, currencyPair currency.Pair, asset asset.Item, action string) (int64, error) {
|
||||
func Add(exchange, item string, condition EventConditionParams, p currency.Pair, a asset.Item, action string) (int64, error) {
|
||||
err := IsValidEvent(exchange, item, condition, action)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -89,8 +89,8 @@ func Add(exchange, item string, condition EventConditionParams, currencyPair cur
|
||||
evt.Exchange = exchange
|
||||
evt.Item = item
|
||||
evt.Condition = condition
|
||||
evt.Pair = currencyPair
|
||||
evt.Asset = asset
|
||||
evt.Pair = p
|
||||
evt.Asset = a
|
||||
evt.Action = action
|
||||
evt.Executed = false
|
||||
Events = append(Events, evt)
|
||||
@@ -99,8 +99,8 @@ func Add(exchange, item string, condition EventConditionParams, currencyPair cur
|
||||
|
||||
// Remove deletes and event by its ID
|
||||
func Remove(eventID int64) bool {
|
||||
for i, x := range Events {
|
||||
if x.ID == eventID {
|
||||
for i := range Events {
|
||||
if Events[i].ID == eventID {
|
||||
Events = append(Events[:i], Events[i+1:]...)
|
||||
return true
|
||||
}
|
||||
@@ -112,9 +112,8 @@ func Remove(eventID int64) bool {
|
||||
// events that have been executed.
|
||||
func GetEventCounter() (total, executed int) {
|
||||
total = len(Events)
|
||||
|
||||
for _, x := range Events {
|
||||
if x.Executed {
|
||||
for i := range Events {
|
||||
if Events[i].Executed {
|
||||
executed++
|
||||
}
|
||||
}
|
||||
@@ -126,11 +125,10 @@ func (e *Event) ExecuteAction() bool {
|
||||
if strings.Contains(e.Action, ",") {
|
||||
action := strings.Split(e.Action, ",")
|
||||
if action[0] == ActionSMSNotify {
|
||||
message := fmt.Sprintf("Event triggered: %s\n", e.String())
|
||||
if action[1] == "ALL" {
|
||||
Bot.CommsManager.PushEvent(base.Event{
|
||||
Type: "event",
|
||||
Message: message,
|
||||
Message: "Event triggered: " + e.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,10 +138,11 @@ func TestProcessTicker(t *testing.T) {
|
||||
|
||||
// now populate it with a 0 entry
|
||||
tick := ticker.Price{
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
Last: 0,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
ExchangeName: e.Exchange,
|
||||
AssetType: e.Asset,
|
||||
}
|
||||
if err := ticker.ProcessTicker(e.Exchange, &tick, e.Asset); err != nil {
|
||||
if err := ticker.ProcessTicker(&tick); err != nil {
|
||||
t.Fatal("unexpected result:", err)
|
||||
}
|
||||
if r := e.processTicker(); r {
|
||||
@@ -150,7 +151,7 @@ func TestProcessTicker(t *testing.T) {
|
||||
|
||||
// now populate it with a number > 0
|
||||
tick.Last = 1337
|
||||
if err := ticker.ProcessTicker(e.Exchange, &tick, e.Asset); err != nil {
|
||||
if err := ticker.ProcessTicker(&tick); err != nil {
|
||||
t.Fatal("unexpected result:", err)
|
||||
}
|
||||
if r := e.processTicker(); !r {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"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/binance"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/bitfinex"
|
||||
@@ -237,7 +238,11 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
|
||||
dryrunParamInteraction("enableallpairs")
|
||||
assets := exchCfg.CurrencyPairs.GetAssetTypes()
|
||||
for x := range assets {
|
||||
pairs := exchCfg.CurrencyPairs.GetPairs(assets[x], false)
|
||||
var pairs currency.Pairs
|
||||
pairs, err = exchCfg.CurrencyPairs.GetPairs(assets[x], false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exchCfg.CurrencyPairs.StorePairs(assets[x], pairs, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
@@ -89,11 +89,11 @@ func (h *FakePassingExchange) FetchTradablePairs(_ asset.Item) ([]string, error)
|
||||
}
|
||||
func (h *FakePassingExchange) UpdateTradablePairs(_ bool) error { return nil }
|
||||
|
||||
func (h *FakePassingExchange) GetEnabledPairs(_ asset.Item) currency.Pairs {
|
||||
return currency.Pairs{}
|
||||
func (h *FakePassingExchange) GetEnabledPairs(_ asset.Item) (currency.Pairs, error) {
|
||||
return currency.Pairs{}, nil
|
||||
}
|
||||
func (h *FakePassingExchange) GetAvailablePairs(_ asset.Item) currency.Pairs {
|
||||
return currency.Pairs{}
|
||||
func (h *FakePassingExchange) GetAvailablePairs(_ asset.Item) (currency.Pairs, error) {
|
||||
return currency.Pairs{}, nil
|
||||
}
|
||||
func (h *FakePassingExchange) FetchAccountInfo() (account.Holdings, error) {
|
||||
return account.Holdings{}, nil
|
||||
@@ -142,6 +142,11 @@ func (h *FakePassingExchange) GetOrderHistory(_ *order.GetOrdersRequest) ([]orde
|
||||
return nil, nil
|
||||
}
|
||||
func (h *FakePassingExchange) GetActiveOrders(_ *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
pair, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []order.Detail{
|
||||
{
|
||||
Price: 1337,
|
||||
@@ -153,25 +158,25 @@ func (h *FakePassingExchange) GetActiveOrders(_ *order.GetOrdersRequest) ([]orde
|
||||
Status: order.Active,
|
||||
AssetType: asset.Spot,
|
||||
Date: time.Now(),
|
||||
Pair: currency.NewPairFromString("BTCUSD"),
|
||||
Pair: pair,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
func (h *FakePassingExchange) SetHTTPClientUserAgent(_ string) {}
|
||||
func (h *FakePassingExchange) GetHTTPClientUserAgent() string { return "" }
|
||||
func (h *FakePassingExchange) SetClientProxyAddress(_ string) error { return nil }
|
||||
func (h *FakePassingExchange) SupportsWebsocket() bool { return true }
|
||||
func (h *FakePassingExchange) SupportsREST() bool { return true }
|
||||
func (h *FakePassingExchange) IsWebsocketEnabled() bool { return true }
|
||||
func (h *FakePassingExchange) GetWebsocket() (*wshandler.Websocket, error) { return nil, nil }
|
||||
func (h *FakePassingExchange) SubscribeToWebsocketChannels(_ []wshandler.WebsocketChannelSubscription) error {
|
||||
func (h *FakePassingExchange) SetHTTPClientUserAgent(_ string) {}
|
||||
func (h *FakePassingExchange) GetHTTPClientUserAgent() string { return "" }
|
||||
func (h *FakePassingExchange) SetClientProxyAddress(_ string) error { return nil }
|
||||
func (h *FakePassingExchange) SupportsWebsocket() bool { return true }
|
||||
func (h *FakePassingExchange) SupportsREST() bool { return true }
|
||||
func (h *FakePassingExchange) IsWebsocketEnabled() bool { return true }
|
||||
func (h *FakePassingExchange) GetWebsocket() (*stream.Websocket, error) { return nil, nil }
|
||||
func (h *FakePassingExchange) SubscribeToWebsocketChannels(_ []stream.ChannelSubscription) error {
|
||||
return nil
|
||||
}
|
||||
func (h *FakePassingExchange) UnsubscribeToWebsocketChannels(_ []wshandler.WebsocketChannelSubscription) error {
|
||||
func (h *FakePassingExchange) UnsubscribeToWebsocketChannels(_ []stream.ChannelSubscription) error {
|
||||
return nil
|
||||
}
|
||||
func (h *FakePassingExchange) AuthenticateWebsocket() error { return nil }
|
||||
func (h *FakePassingExchange) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
func (h *FakePassingExchange) GetSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (h *FakePassingExchange) GetDefaultConfig() (*config.ExchangeConfig, error) { return nil, nil }
|
||||
|
||||
@@ -292,8 +292,7 @@ func MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnly bool, assetT
|
||||
continue
|
||||
}
|
||||
exchName := Bot.Config.Exchanges[y].Name
|
||||
success, err := Bot.Config.SupportsPair(exchName, p[x], assetType)
|
||||
if err != nil || !success {
|
||||
if !Bot.Config.SupportsPair(exchName, p[x], assetType) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -324,14 +323,10 @@ func GetExchangeNamesByCurrency(p currency.Pair, enabled bool, assetType asset.I
|
||||
}
|
||||
|
||||
exchName := Bot.Config.Exchanges[x].Name
|
||||
success, err := Bot.Config.SupportsPair(exchName, p, assetType)
|
||||
if err != nil {
|
||||
if !Bot.Config.SupportsPair(exchName, p, assetType) {
|
||||
continue
|
||||
}
|
||||
|
||||
if success {
|
||||
exchanges = append(exchanges, exchName)
|
||||
}
|
||||
exchanges = append(exchanges, exchName)
|
||||
}
|
||||
return exchanges
|
||||
}
|
||||
@@ -404,19 +399,18 @@ func GetRelatableCurrencies(p currency.Pair, incOrig, incUSDT bool) currency.Pai
|
||||
addPair(p)
|
||||
}
|
||||
|
||||
first, ok := currency.GetTranslation(p.Base)
|
||||
if ok {
|
||||
first := currency.GetTranslation(p.Base)
|
||||
if first != p.Base {
|
||||
addPair(currency.NewPair(first, p.Quote))
|
||||
|
||||
var second currency.Code
|
||||
second, ok = currency.GetTranslation(p.Quote)
|
||||
if ok {
|
||||
second := currency.GetTranslation(p.Quote)
|
||||
if second != p.Quote {
|
||||
addPair(currency.NewPair(first, second))
|
||||
}
|
||||
}
|
||||
|
||||
second, ok := currency.GetTranslation(p.Quote)
|
||||
if ok {
|
||||
second := currency.GetTranslation(p.Quote)
|
||||
if second != p.Quote {
|
||||
addPair(currency.NewPair(p.Base, second))
|
||||
}
|
||||
}
|
||||
@@ -483,8 +477,8 @@ func GetCollatedExchangeAccountInfoByCoin(accounts []account.Holdings) map[curre
|
||||
|
||||
// GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest
|
||||
// price for a given currency pair and asset type
|
||||
func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, assetType asset.Item) (string, error) {
|
||||
result := stats.SortExchangesByPrice(p, assetType, true)
|
||||
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")
|
||||
}
|
||||
@@ -717,7 +711,14 @@ func GetAllActiveTickers() []EnabledExchangeCurrencies {
|
||||
exchangeTicker.ExchangeName = exchName
|
||||
|
||||
for y := range assets {
|
||||
currencies := exchanges[x].GetEnabledPairs(assets[y])
|
||||
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 {
|
||||
@@ -739,19 +740,25 @@ func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts {
|
||||
var response AllEnabledExchangeAccounts
|
||||
exchanges := GetExchanges()
|
||||
for x := range exchanges {
|
||||
if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) {
|
||||
if Bot.Settings.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.\n", exchanges[x].GetName())
|
||||
if exchanges[x] != nil && exchanges[x].IsEnabled() {
|
||||
if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) {
|
||||
if Bot.Settings.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.\n",
|
||||
exchanges[x].GetName())
|
||||
}
|
||||
continue
|
||||
}
|
||||
continue
|
||||
accountInfo, err := exchanges[x].FetchAccountInfo()
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Error encountered retrieving exchange account info for %s. Error %s\n",
|
||||
exchanges[x].GetName(),
|
||||
err)
|
||||
continue
|
||||
}
|
||||
response.Data = append(response.Data, accountInfo)
|
||||
}
|
||||
accountInfo, err := exchanges[x].FetchAccountInfo()
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "Error encountered retrieving exchange account info for %s. Error %s\n",
|
||||
exchanges[x].GetName(), err)
|
||||
continue
|
||||
}
|
||||
response.Data = append(response.Data, accountInfo)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -171,136 +171,225 @@ func TestGetSpecificAvailablePairs(t *testing.T) {
|
||||
assetType := asset.Spot
|
||||
result := GetSpecificAvailablePairs(true, true, true, false, assetType)
|
||||
|
||||
if !result.Contains(currency.NewPairFromStrings("BTC", "USD"), true) {
|
||||
btsusd, err := currency.NewPairFromStrings("BTC", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !result.Contains(btsusd, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
if !result.Contains(currency.NewPairFromStrings("BTC", "USDT"), false) {
|
||||
btcusdt, err := currency.NewPairFromStrings("BTC", "USDT")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !result.Contains(btcusdt, false) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
result = GetSpecificAvailablePairs(true, true, false, false, assetType)
|
||||
|
||||
if result.Contains(currency.NewPairFromStrings("BTC", "USDT"), false) {
|
||||
if result.Contains(btcusdt, false) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
ltcbtc, err := currency.NewPairFromStrings("LTC", "BTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result = GetSpecificAvailablePairs(true, false, false, true, assetType)
|
||||
if !result.Contains(currency.NewPairFromStrings("LTC", "BTC"), false) {
|
||||
if !result.Contains(ltcbtc, false) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRelatablePairs(t *testing.T) {
|
||||
SetupTestHelpers(t)
|
||||
xbtusd, err := currency.NewPairFromStrings("XBT", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btcusd, err := currency.NewPairFromStrings("BTC", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relational pairs with similar names
|
||||
result := IsRelatablePairs(currency.NewPairFromStrings("XBT", "USD"),
|
||||
currency.NewPairFromStrings("BTC", "USD"), false)
|
||||
result := IsRelatablePairs(xbtusd, btcusd, false)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
// Test relational pairs with similar names reversed
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("BTC", "USD"),
|
||||
currency.NewPairFromStrings("XBT", "USD"), false)
|
||||
result = IsRelatablePairs(btcusd, xbtusd, false)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
btcusdt, err := currency.NewPairFromStrings("BTC", "USDT")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relational pairs with similar names but with Tether support disabled
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("XBT", "USD"),
|
||||
currency.NewPairFromStrings("BTC", "USDT"), false)
|
||||
result = IsRelatablePairs(xbtusd, btcusdt, false)
|
||||
if result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
xbtusdt, err := currency.NewPairFromStrings("XBT", "USDT")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relational pairs with similar names but with Tether support enabled
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("XBT", "USDT"),
|
||||
currency.NewPairFromStrings("BTC", "USD"), true)
|
||||
result = IsRelatablePairs(xbtusdt, btcusd, true)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
aeusdt, err := currency.NewPairFromStrings("AE", "USDT")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
usdtae, err := currency.NewPairDelimiter("USDT-AE", "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relational pairs with different ordering, a delimiter and with
|
||||
// Tether support enabled
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("AE", "USDT"),
|
||||
currency.NewPairDelimiter("USDT-AE", "-"), true)
|
||||
result = IsRelatablePairs(aeusdt, usdtae, true)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
// Test relational pairs with different ordering, a delimiter and with
|
||||
// Tether support disabled
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("AE", "USDT"),
|
||||
currency.NewPairDelimiter("USDT-AE", "-"), false)
|
||||
result = IsRelatablePairs(aeusdt, usdtae, false)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
xbteur, err := currency.NewPairFromStrings("XBT", "EUR")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btcaud, err := currency.NewPairFromStrings("BTC", "AUD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relationl pairs with similar names and different fiat currencies
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("XBT", "EUR"),
|
||||
currency.NewPairFromStrings("BTC", "AUD"), false)
|
||||
result = IsRelatablePairs(xbteur, btcaud, false)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
usdbtc, err := currency.NewPairFromStrings("USD", "BTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btceur, err := currency.NewPairFromStrings("BTC", "EUR")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relationl pairs with similar names, different fiat currencies and
|
||||
// with different ordering
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("USD", "BTC"),
|
||||
currency.NewPairFromStrings("BTC", "EUR"), false)
|
||||
result = IsRelatablePairs(usdbtc, btceur, false)
|
||||
if !result { // Is this really expected result???
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
// Test relationl pairs with similar names, different fiat currencies and
|
||||
// with Tether enabled
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("USD", "BTC"),
|
||||
currency.NewPairFromStrings("BTC", "USDT"), true)
|
||||
result = IsRelatablePairs(usdbtc, btcusdt, true)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
ltcbtc, err := currency.NewPairFromStrings("LTC", "BTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btcltc, err := currency.NewPairFromStrings("BTC", "LTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relationl crypto pairs with similar names
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("LTC", "BTC"),
|
||||
currency.NewPairFromStrings("BTC", "LTC"), false)
|
||||
result = IsRelatablePairs(ltcbtc, btcltc, false)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
ltceth, err := currency.NewPairFromStrings("LTC", "ETH")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btceth, err := currency.NewPairFromStrings("BTC", "ETH")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relationl crypto pairs with similar different pairs
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("LTC", "ETH"),
|
||||
currency.NewPairFromStrings("BTC", "ETH"), false)
|
||||
result = IsRelatablePairs(ltceth, btceth, false)
|
||||
if result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
// Test relationl crypto pairs with similar different pairs and with USDT
|
||||
// enabled
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("USDT", "USD"),
|
||||
currency.NewPairFromStrings("BTC", "USD"), true)
|
||||
usdtusd, err := currency.NewPairFromStrings("USDT", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result = IsRelatablePairs(usdtusd, btcusd, true)
|
||||
if result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
xbtltc, err := currency.NewPairFromStrings("XBT", "LTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relationl crypto pairs with with similar names
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("XBT", "LTC"),
|
||||
currency.NewPairFromStrings("BTC", "LTC"), false)
|
||||
result = IsRelatablePairs(xbtltc, btcltc, false)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
ltcxbt, err := currency.NewPairFromStrings("LTC", "XBT")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test relationl crypto pairs with different ordering and similar names
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("LTC", "XBT"),
|
||||
currency.NewPairFromStrings("BTC", "LTC"), false)
|
||||
result = IsRelatablePairs(ltcxbt, btcltc, false)
|
||||
if !result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
// Test edge case between two pairs when currency translations were causing
|
||||
// non-relational pairs to be relatable
|
||||
result = IsRelatablePairs(currency.NewPairFromStrings("EUR", "USD"),
|
||||
currency.NewPairFromStrings("BTC", "USD"), false)
|
||||
eurusd, err := currency.NewPairFromStrings("EUR", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result = IsRelatablePairs(eurusd, btcusd, false)
|
||||
if result {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
@@ -308,44 +397,80 @@ func TestIsRelatablePairs(t *testing.T) {
|
||||
|
||||
func TestGetRelatableCryptocurrencies(t *testing.T) {
|
||||
SetupTestHelpers(t)
|
||||
p := GetRelatableCryptocurrencies(currency.NewPairFromStrings("BTC", "LTC"))
|
||||
if p.Contains(currency.NewPairFromStrings("BTC", "LTC"), true) {
|
||||
btcltc, err := currency.NewPairFromStrings("BTC", "LTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btcbtc, err := currency.NewPairFromStrings("BTC", "BTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ltcltc, err := currency.NewPairFromStrings("LTC", "LTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btceth, err := currency.NewPairFromStrings("BTC", "ETH")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p := GetRelatableCryptocurrencies(btcltc)
|
||||
if p.Contains(btcltc, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
if p.Contains(currency.NewPairFromStrings("BTC", "BTC"), true) {
|
||||
if p.Contains(btcbtc, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
if p.Contains(currency.NewPairFromStrings("LTC", "LTC"), true) {
|
||||
if p.Contains(ltcltc, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
if !p.Contains(currency.NewPairFromStrings("BTC", "ETH"), true) {
|
||||
if !p.Contains(btceth, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
p = GetRelatableCryptocurrencies(currency.NewPairFromStrings("BTC", "LTC"))
|
||||
if p.Contains(currency.NewPairFromStrings("BTC", "LTC"), true) {
|
||||
p = GetRelatableCryptocurrencies(btcltc)
|
||||
if p.Contains(btcltc, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
if p.Contains(currency.NewPairFromStrings("BTC", "BTC"), true) {
|
||||
if p.Contains(btcbtc, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
if p.Contains(currency.NewPairFromStrings("LTC", "LTC"), true) {
|
||||
if p.Contains(ltcltc, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
if !p.Contains(currency.NewPairFromStrings("BTC", "ETH"), true) {
|
||||
if !p.Contains(btceth, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRelatableFiatCurrencies(t *testing.T) {
|
||||
SetupTestHelpers(t)
|
||||
p := GetRelatableFiatCurrencies(currency.NewPairFromStrings("BTC", "USD"))
|
||||
if !p.Contains(currency.NewPairFromStrings("BTC", "EUR"), true) {
|
||||
|
||||
btsusd, err := currency.NewPairFromStrings("BTC", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btceur, err := currency.NewPairFromStrings("BTC", "EUR")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p := GetRelatableFiatCurrencies(btsusd)
|
||||
if !p.Contains(btceur, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
p = GetRelatableFiatCurrencies(currency.NewPairFromStrings("BTC", "USD"))
|
||||
if !p.Contains(currency.NewPairFromStrings("BTC", "ZAR"), true) {
|
||||
btczar, err := currency.NewPairFromStrings("BTC", "ZAR")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p = GetRelatableFiatCurrencies(btsusd)
|
||||
if !p.Contains(btczar, true) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
}
|
||||
@@ -373,21 +498,36 @@ func TestGetExchangeNamesByCurrency(t *testing.T) {
|
||||
SetupTestHelpers(t)
|
||||
assetType := asset.Spot
|
||||
|
||||
result := GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "USD"),
|
||||
btsusd, err := currency.NewPairFromStrings("BTC", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
btcjpy, err := currency.NewPairFromStrings("BTC", "JPY")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blahjpy, err := currency.NewPairFromStrings("blah", "JPY")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result := GetExchangeNamesByCurrency(btsusd,
|
||||
true,
|
||||
assetType)
|
||||
if !common.StringDataCompare(result, "Bitstamp") {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "JPY"),
|
||||
result = GetExchangeNamesByCurrency(btcjpy,
|
||||
true,
|
||||
assetType)
|
||||
if !common.StringDataCompare(result, "Bitflyer") {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("blah", "JPY"),
|
||||
result = GetExchangeNamesByCurrency(blahjpy,
|
||||
true,
|
||||
assetType)
|
||||
if len(result) > 0 {
|
||||
@@ -415,9 +555,12 @@ func TestGetSpecificOrderbook(t *testing.T) {
|
||||
t.Fatal("Unexpected result", err)
|
||||
}
|
||||
|
||||
ob, err := GetSpecificOrderbook(currency.NewPairFromString("BTCUSD"),
|
||||
"Bitstamp",
|
||||
asset.Spot)
|
||||
btsusd, err := currency.NewPairFromStrings("BTC", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ob, err := GetSpecificOrderbook(btsusd, "Bitstamp", asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -426,9 +569,12 @@ func TestGetSpecificOrderbook(t *testing.T) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
_, err = GetSpecificOrderbook(currency.NewPairFromStrings("ETH", "LTC"),
|
||||
"Bitstamp",
|
||||
asset.Spot)
|
||||
ethltc, err := currency.NewPairFromStrings("ETH", "LTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = GetSpecificOrderbook(ethltc, "Bitstamp", asset.Spot)
|
||||
if err == nil {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
@@ -440,16 +586,21 @@ func TestGetSpecificTicker(t *testing.T) {
|
||||
SetupTestHelpers(t)
|
||||
|
||||
LoadExchange("Bitstamp", false, nil)
|
||||
p := currency.NewPairFromStrings("BTC", "USD")
|
||||
err := ticker.ProcessTicker("Bitstamp",
|
||||
&ticker.Price{Pair: p, Last: 1000},
|
||||
asset.Spot)
|
||||
p, err := currency.NewPairFromStrings("BTC", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Pair: p,
|
||||
Last: 1000,
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: "Bitstamp"})
|
||||
if err != nil {
|
||||
t.Fatal("ProcessTicker error", err)
|
||||
}
|
||||
|
||||
tick, err := GetSpecificTicker(currency.NewPairFromStrings("BTC", "USD"), "Bitstamp",
|
||||
asset.Spot)
|
||||
tick, err := GetSpecificTicker(p, "Bitstamp", asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -458,8 +609,12 @@ func TestGetSpecificTicker(t *testing.T) {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
|
||||
_, err = GetSpecificTicker(currency.NewPairFromStrings("ETH", "LTC"), "Bitstamp",
|
||||
asset.Spot)
|
||||
ethltc, err := currency.NewPairFromStrings("ETH", "LTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = GetSpecificTicker(ethltc, "Bitstamp", asset.Spot)
|
||||
if err == nil {
|
||||
t.Fatal("Unexpected result")
|
||||
}
|
||||
@@ -530,7 +685,11 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) {
|
||||
func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) {
|
||||
SetupTestHelpers(t)
|
||||
|
||||
p := currency.NewPairFromStrings("BTC", "USD")
|
||||
p, err := currency.NewPairFromStrings("BTC", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stats.Add("Bitfinex", p, asset.Spot, 1000, 10000)
|
||||
stats.Add("Bitstamp", p, asset.Spot, 1337, 10000)
|
||||
exchangeName, err := GetExchangeHighestPriceByCurrencyPair(p, asset.Spot)
|
||||
@@ -542,8 +701,12 @@ func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) {
|
||||
t.Error("Unexpected result")
|
||||
}
|
||||
|
||||
_, err = GetExchangeHighestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"),
|
||||
asset.Spot)
|
||||
btcaud, err := currency.NewPairFromStrings("BTC", "AUD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = GetExchangeHighestPriceByCurrencyPair(btcaud, asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("Unexpected result")
|
||||
}
|
||||
@@ -552,7 +715,11 @@ func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) {
|
||||
func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) {
|
||||
SetupTestHelpers(t)
|
||||
|
||||
p := currency.NewPairFromStrings("BTC", "USD")
|
||||
p, err := currency.NewPairFromStrings("BTC", "USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stats.Add("Bitfinex", p, asset.Spot, 1000, 10000)
|
||||
stats.Add("Bitstamp", p, asset.Spot, 1337, 10000)
|
||||
exchangeName, err := GetExchangeLowestPriceByCurrencyPair(p, asset.Spot)
|
||||
@@ -564,8 +731,12 @@ func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) {
|
||||
t.Error("Unexpected result")
|
||||
}
|
||||
|
||||
_, err = GetExchangeLowestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"),
|
||||
asset.Spot)
|
||||
btcaud, err := currency.NewPairFromStrings("BTC", "AUD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = GetExchangeLowestPriceByCurrencyPair(btcaud, asset.Spot)
|
||||
if err == nil {
|
||||
t.Error("Unexpected reuslt")
|
||||
}
|
||||
|
||||
@@ -389,18 +389,45 @@ func (o *orderManager) Submit(newOrder *order.Submit) (*orderSubmitResponse, err
|
||||
func (o *orderManager) processOrders() {
|
||||
authExchanges := GetAuthAPISupportedExchanges()
|
||||
for x := range authExchanges {
|
||||
log.Debugf(log.OrderMgr, "Order manager: Processing orders for exchange %v.", authExchanges[x])
|
||||
log.Debugf(log.OrderMgr,
|
||||
"Order manager: Processing orders for exchange %v.",
|
||||
authExchanges[x])
|
||||
|
||||
exch := GetExchangeByName(authExchanges[x])
|
||||
supportedAssets := exch.GetAssetTypes()
|
||||
for y := range supportedAssets {
|
||||
pairs, err := exch.GetEnabledPairs(supportedAssets[y])
|
||||
if err != nil {
|
||||
log.Errorf(log.OrderMgr,
|
||||
"Order manager: Unable to get enabled pairs for %s and asset type %s: %s",
|
||||
authExchanges[x],
|
||||
supportedAssets[y],
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(pairs) == 0 {
|
||||
if Bot.Settings.Verbose {
|
||||
log.Debugf(log.OrderMgr,
|
||||
"Order manager: No pairs enabled for %s and asset type %s, skipping...",
|
||||
authExchanges[x],
|
||||
supportedAssets[y])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
req := order.GetOrdersRequest{
|
||||
Side: order.AnySide,
|
||||
Type: order.AnyType,
|
||||
Pairs: exch.GetEnabledPairs(supportedAssets[y]),
|
||||
Pairs: pairs,
|
||||
}
|
||||
result, err := exch.GetActiveOrders(&req)
|
||||
if err != nil {
|
||||
log.Warnf(log.OrderMgr, "Order manager: Unable to get active orders: %s", err)
|
||||
log.Warnf(log.OrderMgr,
|
||||
"Order manager: Unable to get active orders for %s and asset type %s: %s",
|
||||
authExchanges[x],
|
||||
supportedAssets[y],
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,11 @@ func TestCancelOrder(t *testing.T) {
|
||||
t.Error("Expected error due to no order found")
|
||||
}
|
||||
|
||||
pair, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cancel := &order.Cancel{
|
||||
Exchange: fakePassExchange,
|
||||
ID: "TestCancelOrder",
|
||||
@@ -261,7 +266,7 @@ func TestCancelOrder(t *testing.T) {
|
||||
Status: order.New,
|
||||
AssetType: asset.Spot,
|
||||
Date: time.Now(),
|
||||
Pair: currency.NewPairFromString("BTCUSD"),
|
||||
Pair: pair,
|
||||
}
|
||||
err = Bot.OrderManager.Cancel(cancel)
|
||||
if err != nil {
|
||||
@@ -326,9 +331,14 @@ func TestSubmit(t *testing.T) {
|
||||
t.Error("Expected error from validation")
|
||||
}
|
||||
|
||||
pair, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Bot.OrderManager.cfg.EnforceLimitConfig = true
|
||||
Bot.OrderManager.cfg.AllowMarketOrders = false
|
||||
o.Pair = currency.NewPairFromString("BTCUSD")
|
||||
o.Pair = pair
|
||||
o.AssetType = asset.Spot
|
||||
o.Side = order.Buy
|
||||
o.Amount = 1
|
||||
@@ -351,8 +361,13 @@ func TestSubmit(t *testing.T) {
|
||||
t.Error("Expected fail due to order exchange not found in allowed list")
|
||||
}
|
||||
|
||||
failPair, err := currency.NewPairFromString("BTCAUD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Bot.OrderManager.cfg.AllowedExchanges = nil
|
||||
Bot.OrderManager.cfg.AllowedPairs = currency.Pairs{currency.NewPairFromString("BTCAUD")}
|
||||
Bot.OrderManager.cfg.AllowedPairs = currency.Pairs{failPair}
|
||||
_, err = Bot.OrderManager.Submit(o)
|
||||
if err == nil {
|
||||
t.Error("Expected fail due to order pair not found in allowed list")
|
||||
|
||||
@@ -66,7 +66,14 @@ func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks {
|
||||
exchangeOB.ExchangeName = exchName
|
||||
|
||||
for y := range assets {
|
||||
currencies := exchanges[x].GetEnabledPairs(assets[y])
|
||||
currencies, err := exchanges[x].GetEnabledPairs(assets[y])
|
||||
if err != nil {
|
||||
log.Errorf(log.RESTSys,
|
||||
"Exchange %s could not retrieve enabled currencies. Err: %s\n",
|
||||
exchName,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
for z := range currencies {
|
||||
ob, err := exchanges[x].FetchOrderbook(currencies[z], assets[y])
|
||||
if err != nil {
|
||||
|
||||
@@ -8,12 +8,11 @@ import (
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
@@ -57,26 +56,29 @@ func printConvertCurrencyFormat(origCurrency currency.Code, origPrice float64) s
|
||||
)
|
||||
}
|
||||
|
||||
func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.Item, exchangeName, protocol string, err error) {
|
||||
func printTickerSummary(result *ticker.Price, protocol string, err error) {
|
||||
if err != nil {
|
||||
log.Errorf(log.Ticker, "Failed to get %s %s %s %s ticker. Error: %s\n",
|
||||
exchangeName,
|
||||
if err == common.ErrNotYetImplemented {
|
||||
log.Warnf(log.Ticker, "Failed to get %s ticker. Error: %s\n",
|
||||
protocol,
|
||||
err)
|
||||
return
|
||||
}
|
||||
log.Errorf(log.Ticker, "Failed to get %s ticker. Error: %s\n",
|
||||
protocol,
|
||||
p,
|
||||
assetType,
|
||||
err)
|
||||
return
|
||||
}
|
||||
|
||||
stats.Add(exchangeName, p, assetType, result.Last, result.Volume)
|
||||
if p.Quote.IsFiatCurrency() &&
|
||||
p.Quote != Bot.Config.Currency.FiatDisplayCurrency {
|
||||
origCurrency := p.Quote.Upper()
|
||||
stats.Add(result.ExchangeName, result.Pair, result.AssetType, result.Last, result.Volume)
|
||||
if result.Pair.Quote.IsFiatCurrency() &&
|
||||
result.Pair.Quote != Bot.Config.Currency.FiatDisplayCurrency {
|
||||
origCurrency := result.Pair.Quote.Upper()
|
||||
log.Infof(log.Ticker, "%s %s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f\n",
|
||||
exchangeName,
|
||||
result.ExchangeName,
|
||||
protocol,
|
||||
FormatCurrency(p),
|
||||
strings.ToUpper(assetType.String()),
|
||||
FormatCurrency(result.Pair),
|
||||
strings.ToUpper(result.AssetType.String()),
|
||||
printConvertCurrencyFormat(origCurrency, result.Last),
|
||||
printConvertCurrencyFormat(origCurrency, result.Ask),
|
||||
printConvertCurrencyFormat(origCurrency, result.Bid),
|
||||
@@ -84,13 +86,13 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I
|
||||
printConvertCurrencyFormat(origCurrency, result.Low),
|
||||
result.Volume)
|
||||
} else {
|
||||
if p.Quote.IsFiatCurrency() &&
|
||||
p.Quote == Bot.Config.Currency.FiatDisplayCurrency {
|
||||
if result.Pair.Quote.IsFiatCurrency() &&
|
||||
result.Pair.Quote == Bot.Config.Currency.FiatDisplayCurrency {
|
||||
log.Infof(log.Ticker, "%s %s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f\n",
|
||||
exchangeName,
|
||||
result.ExchangeName,
|
||||
protocol,
|
||||
FormatCurrency(p),
|
||||
strings.ToUpper(assetType.String()),
|
||||
FormatCurrency(result.Pair),
|
||||
strings.ToUpper(result.AssetType.String()),
|
||||
printCurrencyFormat(result.Last),
|
||||
printCurrencyFormat(result.Ask),
|
||||
printCurrencyFormat(result.Bid),
|
||||
@@ -99,10 +101,10 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I
|
||||
result.Volume)
|
||||
} else {
|
||||
log.Infof(log.Ticker, "%s %s %s %s: TICKER: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f\n",
|
||||
exchangeName,
|
||||
result.ExchangeName,
|
||||
protocol,
|
||||
FormatCurrency(p),
|
||||
strings.ToUpper(assetType.String()),
|
||||
FormatCurrency(result.Pair),
|
||||
strings.ToUpper(result.AssetType.String()),
|
||||
result.Last,
|
||||
result.Ask,
|
||||
result.Bid,
|
||||
@@ -113,13 +115,16 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I
|
||||
}
|
||||
}
|
||||
|
||||
func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType asset.Item, exchangeName, protocol string, err error) {
|
||||
func printOrderbookSummary(result *orderbook.Base, protocol string, err error) {
|
||||
if err != nil {
|
||||
log.Errorf(log.OrderBook, "Failed to get %s %s %s orderbook of type %s. Error: %s\n",
|
||||
exchangeName,
|
||||
if err == common.ErrNotYetImplemented {
|
||||
log.Warnf(log.Ticker, "Failed to get %s ticker. Error: %s\n",
|
||||
protocol,
|
||||
err)
|
||||
return
|
||||
}
|
||||
log.Errorf(log.OrderBook, "Failed to get %s orderbook. Error: %s\n",
|
||||
protocol,
|
||||
p,
|
||||
assetType,
|
||||
err)
|
||||
return
|
||||
}
|
||||
@@ -127,53 +132,53 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType as
|
||||
bidsAmount, bidsValue := result.TotalBidsAmount()
|
||||
asksAmount, asksValue := result.TotalAsksAmount()
|
||||
|
||||
if p.Quote.IsFiatCurrency() &&
|
||||
p.Quote != Bot.Config.Currency.FiatDisplayCurrency {
|
||||
origCurrency := p.Quote.Upper()
|
||||
if result.Pair.Quote.IsFiatCurrency() &&
|
||||
result.Pair.Quote != Bot.Config.Currency.FiatDisplayCurrency {
|
||||
origCurrency := result.Pair.Quote.Upper()
|
||||
log.Infof(log.OrderBook, "%s %s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s\n",
|
||||
exchangeName,
|
||||
result.ExchangeName,
|
||||
protocol,
|
||||
FormatCurrency(p),
|
||||
strings.ToUpper(assetType.String()),
|
||||
FormatCurrency(result.Pair),
|
||||
strings.ToUpper(result.AssetType.String()),
|
||||
len(result.Bids),
|
||||
bidsAmount,
|
||||
p.Base,
|
||||
result.Pair.Base,
|
||||
printConvertCurrencyFormat(origCurrency, bidsValue),
|
||||
len(result.Asks),
|
||||
asksAmount,
|
||||
p.Base,
|
||||
result.Pair.Base,
|
||||
printConvertCurrencyFormat(origCurrency, asksValue),
|
||||
)
|
||||
} else {
|
||||
if p.Quote.IsFiatCurrency() &&
|
||||
p.Quote == Bot.Config.Currency.FiatDisplayCurrency {
|
||||
if result.Pair.Quote.IsFiatCurrency() &&
|
||||
result.Pair.Quote == Bot.Config.Currency.FiatDisplayCurrency {
|
||||
log.Infof(log.OrderBook, "%s %s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s\n",
|
||||
exchangeName,
|
||||
result.ExchangeName,
|
||||
protocol,
|
||||
FormatCurrency(p),
|
||||
strings.ToUpper(assetType.String()),
|
||||
FormatCurrency(result.Pair),
|
||||
strings.ToUpper(result.AssetType.String()),
|
||||
len(result.Bids),
|
||||
bidsAmount,
|
||||
p.Base,
|
||||
result.Pair.Base,
|
||||
printCurrencyFormat(bidsValue),
|
||||
len(result.Asks),
|
||||
asksAmount,
|
||||
p.Base,
|
||||
result.Pair.Base,
|
||||
printCurrencyFormat(asksValue),
|
||||
)
|
||||
} else {
|
||||
log.Infof(log.OrderBook, "%s %s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %f Asks len: %d Amount: %f %s. Total value: %f\n",
|
||||
exchangeName,
|
||||
result.ExchangeName,
|
||||
protocol,
|
||||
FormatCurrency(p),
|
||||
strings.ToUpper(assetType.String()),
|
||||
FormatCurrency(result.Pair),
|
||||
strings.ToUpper(result.AssetType.String()),
|
||||
len(result.Bids),
|
||||
bidsAmount,
|
||||
p.Base,
|
||||
result.Pair.Base,
|
||||
bidsValue,
|
||||
len(result.Asks),
|
||||
asksAmount,
|
||||
p.Base,
|
||||
result.Pair.Base,
|
||||
asksValue,
|
||||
)
|
||||
}
|
||||
@@ -212,28 +217,27 @@ func WebsocketRoutine() {
|
||||
)
|
||||
}
|
||||
|
||||
// TO-DO: expose IsConnected() and IsConnecting so this can be simplified
|
||||
if exchanges[i].IsWebsocketEnabled() {
|
||||
ws, err := exchanges[i].GetWebsocket()
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
log.WebsocketMgr,
|
||||
"Exchange %s GetWebsocket error: %s\n",
|
||||
exchanges[i].GetName(),
|
||||
err,
|
||||
)
|
||||
return
|
||||
}
|
||||
ws, err := exchanges[i].GetWebsocket()
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
log.WebsocketMgr,
|
||||
"Exchange %s GetWebsocket error: %s\n",
|
||||
exchanges[i].GetName(),
|
||||
err,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Exchange sync manager might have already started ws
|
||||
// service or is in the process of connecting, so check
|
||||
if ws.IsConnected() || ws.IsConnecting() {
|
||||
return
|
||||
}
|
||||
// Exchange sync manager might have already started ws
|
||||
// service or is in the process of connecting, so check
|
||||
if ws.IsConnected() || ws.IsConnecting() {
|
||||
return
|
||||
}
|
||||
|
||||
// Data handler routine
|
||||
go WebsocketDataReceiver(ws)
|
||||
// Data handler routine
|
||||
go WebsocketDataReceiver(ws)
|
||||
|
||||
if ws.IsEnabled() {
|
||||
err = ws.Connect()
|
||||
if err != nil {
|
||||
log.Errorf(log.WebsocketMgr, "%v\n", err)
|
||||
@@ -254,7 +258,7 @@ var wg sync.WaitGroup
|
||||
|
||||
// WebsocketDataReceiver handles websocket data coming from a websocket feed
|
||||
// associated with an exchange
|
||||
func WebsocketDataReceiver(ws *wshandler.Websocket) {
|
||||
func WebsocketDataReceiver(ws *stream.Websocket) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
|
||||
@@ -262,7 +266,7 @@ func WebsocketDataReceiver(ws *wshandler.Websocket) {
|
||||
select {
|
||||
case <-shutdowner:
|
||||
return
|
||||
case data := <-ws.DataHandler:
|
||||
case data := <-ws.ToRoutine:
|
||||
err := WebsocketDataHandler(ws.GetName(), data)
|
||||
if err != nil {
|
||||
log.Error(log.WebsocketMgr, err)
|
||||
@@ -278,12 +282,13 @@ func WebsocketDataHandler(exchName string, data interface{}) error {
|
||||
return fmt.Errorf("routines.go - exchange %s nil data sent to websocket",
|
||||
exchName)
|
||||
}
|
||||
|
||||
switch d := data.(type) {
|
||||
case string:
|
||||
log.Info(log.WebsocketMgr, d)
|
||||
case error:
|
||||
return fmt.Errorf("routines.go exchange %s websocket error - %s", exchName, data)
|
||||
case wshandler.TradeData:
|
||||
case stream.TradeData:
|
||||
if Bot.Settings.Verbose {
|
||||
log.Infof(log.WebsocketMgr, "%s websocket %s %s trade updated %+v",
|
||||
exchName,
|
||||
@@ -291,7 +296,7 @@ func WebsocketDataHandler(exchName string, data interface{}) error {
|
||||
d.AssetType,
|
||||
d)
|
||||
}
|
||||
case wshandler.FundingData:
|
||||
case stream.FundingData:
|
||||
if Bot.Settings.Verbose {
|
||||
log.Infof(log.WebsocketMgr, "%s websocket %s %s funding updated %+v",
|
||||
exchName,
|
||||
@@ -307,9 +312,9 @@ func WebsocketDataHandler(exchName string, data interface{}) error {
|
||||
SyncItemTicker,
|
||||
nil)
|
||||
}
|
||||
err := ticker.ProcessTicker(exchName, d, d.AssetType)
|
||||
printTickerSummary(d, d.Pair, d.AssetType, exchName, "websocket", err)
|
||||
case wshandler.KlineData:
|
||||
err := ticker.ProcessTicker(d)
|
||||
printTickerSummary(d, "websocket", err)
|
||||
case stream.KlineData:
|
||||
if Bot.Settings.Verbose {
|
||||
log.Infof(log.WebsocketMgr, "%s websocket %s %s kline updated %+v",
|
||||
exchName,
|
||||
@@ -317,22 +322,15 @@ func WebsocketDataHandler(exchName string, data interface{}) error {
|
||||
d.AssetType,
|
||||
d)
|
||||
}
|
||||
case wshandler.WebsocketOrderbookUpdate:
|
||||
case *orderbook.Base:
|
||||
if Bot.Settings.EnableExchangeSyncManager && Bot.ExchangeCurrencyPairManager != nil {
|
||||
Bot.ExchangeCurrencyPairManager.update(exchName,
|
||||
d.Pair,
|
||||
d.Asset,
|
||||
d.AssetType,
|
||||
SyncItemOrderbook,
|
||||
nil)
|
||||
}
|
||||
|
||||
if Bot.Settings.Verbose {
|
||||
log.Infof(log.WebsocketMgr,
|
||||
"%s websocket %s %s orderbook updated",
|
||||
exchName,
|
||||
FormatCurrency(d.Pair),
|
||||
d.Asset)
|
||||
}
|
||||
printOrderbookSummary(d, "websocket", nil)
|
||||
case *order.Detail:
|
||||
if !Bot.OrderManager.orderStore.exists(d) {
|
||||
err := Bot.OrderManager.orderStore.Add(d)
|
||||
@@ -356,7 +354,7 @@ func WebsocketDataHandler(exchName string, data interface{}) error {
|
||||
od.UpdateOrderFromModify(d)
|
||||
case order.ClassificationError:
|
||||
return errors.New(d.Error())
|
||||
case wshandler.UnhandledMessageWarning:
|
||||
case stream.UnhandledMessageWarning:
|
||||
log.Warn(log.WebsocketMgr, d.Message)
|
||||
default:
|
||||
if Bot.Settings.Verbose {
|
||||
|
||||
@@ -5,19 +5,16 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
func TestWebsocketDataHandlerProcess(t *testing.T) {
|
||||
ws := wshandler.New()
|
||||
err := ws.Setup(&wshandler.WebsocketSetup{Enabled: true})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
ws.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
ws := sharedtestvalues.NewTestWebsocket()
|
||||
go WebsocketDataReceiver(ws)
|
||||
ws.DataHandler <- "string"
|
||||
time.Sleep(time.Second)
|
||||
@@ -36,11 +33,11 @@ func TestHandleData(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("Expected nil data error")
|
||||
}
|
||||
err = WebsocketDataHandler(exchName, wshandler.TradeData{})
|
||||
err = WebsocketDataHandler(exchName, stream.TradeData{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = WebsocketDataHandler(exchName, wshandler.FundingData{})
|
||||
err = WebsocketDataHandler(exchName, stream.FundingData{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -48,11 +45,7 @@ func TestHandleData(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = WebsocketDataHandler(exchName, wshandler.KlineData{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = WebsocketDataHandler(exchName, wshandler.WebsocketOrderbookUpdate{})
|
||||
err = WebsocketDataHandler(exchName, stream.KlineData{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -107,7 +100,9 @@ func TestHandleData(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = WebsocketDataHandler(exchName, wshandler.UnhandledMessageWarning{Message: "there's an issue here's a tissue"})
|
||||
err = WebsocketDataHandler(exchName, stream.UnhandledMessageWarning{
|
||||
Message: "there's an issue here's a tissue"},
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -124,4 +119,16 @@ func TestHandleData(t *testing.T) {
|
||||
if err.Error() != classificationError.Error() {
|
||||
t.Errorf("Problem formatting error. Expected %v Received %v", classificationError.Error(), err.Error())
|
||||
}
|
||||
|
||||
err = WebsocketDataHandler(exchName, &orderbook.Base{
|
||||
ExchangeName: fakePassExchange,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = WebsocketDataHandler(exchName, "this is a test string")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -200,16 +200,18 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair
|
||||
}
|
||||
|
||||
switch syncType {
|
||||
case SyncItemOrderbook, SyncItemTrade, SyncItemTicker:
|
||||
if !e.Cfg.SyncOrderbook && syncType == SyncItemOrderbook {
|
||||
case SyncItemOrderbook:
|
||||
if !e.Cfg.SyncOrderbook {
|
||||
return
|
||||
}
|
||||
|
||||
if !e.Cfg.SyncTicker && syncType == SyncItemTicker {
|
||||
case SyncItemTicker:
|
||||
if !e.Cfg.SyncTicker {
|
||||
return
|
||||
}
|
||||
|
||||
if !e.Cfg.SyncTrades && syncType == SyncItemTrade {
|
||||
case SyncItemTrade:
|
||||
if !e.Cfg.SyncTrades {
|
||||
return
|
||||
}
|
||||
default:
|
||||
@@ -236,7 +238,10 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair
|
||||
if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData {
|
||||
removedCounter++
|
||||
log.Debugf(log.SyncMgr, "%s ticker sync complete %v [%d/%d].\n",
|
||||
exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter)
|
||||
exchangeName,
|
||||
FormatCurrency(p).String(),
|
||||
removedCounter,
|
||||
createdCounter)
|
||||
e.initSyncWG.Done()
|
||||
}
|
||||
|
||||
@@ -251,7 +256,10 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair
|
||||
if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData {
|
||||
removedCounter++
|
||||
log.Debugf(log.SyncMgr, "%s orderbook sync complete %v [%d/%d].\n",
|
||||
exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter)
|
||||
exchangeName,
|
||||
FormatCurrency(p).String(),
|
||||
removedCounter,
|
||||
createdCounter)
|
||||
e.initSyncWG.Done()
|
||||
}
|
||||
|
||||
@@ -266,7 +274,10 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair
|
||||
if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData {
|
||||
removedCounter++
|
||||
log.Debugf(log.SyncMgr, "%s trade sync complete %v [%d/%d].\n",
|
||||
exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter)
|
||||
exchangeName,
|
||||
FormatCurrency(p).String(),
|
||||
removedCounter,
|
||||
createdCounter)
|
||||
e.initSyncWG.Done()
|
||||
}
|
||||
}
|
||||
@@ -276,7 +287,8 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair
|
||||
|
||||
func (e *ExchangeCurrencyPairSyncer) worker() {
|
||||
cleanup := func() {
|
||||
log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer worker shutting down.")
|
||||
log.Debugln(log.SyncMgr,
|
||||
"Exchange CurrencyPairSyncer worker shutting down.")
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
@@ -293,8 +305,10 @@ func (e *ExchangeCurrencyPairSyncer) worker() {
|
||||
if exchanges[x].SupportsWebsocket() && exchanges[x].IsWebsocketEnabled() {
|
||||
ws, err := exchanges[x].GetWebsocket()
|
||||
if err != nil {
|
||||
log.Errorf(log.SyncMgr, "%s unable to get websocket pointer. Err: %s\n",
|
||||
exchangeName, err)
|
||||
log.Errorf(log.SyncMgr,
|
||||
"%s unable to get websocket pointer. Err: %s\n",
|
||||
exchangeName,
|
||||
err)
|
||||
usingREST = true
|
||||
}
|
||||
|
||||
@@ -308,7 +322,17 @@ func (e *ExchangeCurrencyPairSyncer) worker() {
|
||||
}
|
||||
|
||||
for y := range assetTypes {
|
||||
enabledPairs := exchanges[x].GetEnabledPairs(assetTypes[y])
|
||||
if exchanges[x].GetBase().CurrencyPairs.IsAssetEnabled(assetTypes[y]) != nil {
|
||||
continue
|
||||
}
|
||||
enabledPairs, err := exchanges[x].GetEnabledPairs(assetTypes[y])
|
||||
if err != nil {
|
||||
log.Errorf(log.SyncMgr,
|
||||
"%s failed to get enabled pairs. Err: %s\n",
|
||||
exchangeName,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
for i := range enabledPairs {
|
||||
if atomic.LoadInt32(&e.shutdown) == 1 {
|
||||
return
|
||||
@@ -410,7 +434,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() {
|
||||
} else {
|
||||
result, err = exchanges[x].UpdateTicker(c.Pair, c.AssetType)
|
||||
}
|
||||
printTickerSummary(result, c.Pair, c.AssetType, exchangeName, "REST", err)
|
||||
printTickerSummary(result, "REST", err)
|
||||
if err == nil {
|
||||
if Bot.Config.RemoteControl.WebsocketRPC.Enabled {
|
||||
relayWebsocketEvent(result, "ticker_update", c.AssetType.String(), exchangeName)
|
||||
@@ -449,7 +473,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() {
|
||||
|
||||
e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, true)
|
||||
result, err := exchanges[x].UpdateOrderbook(c.Pair, c.AssetType)
|
||||
printOrderbookSummary(result, c.Pair, c.AssetType, exchangeName, "REST", err)
|
||||
printOrderbookSummary(result, "REST", err)
|
||||
if err == nil {
|
||||
if Bot.Config.RemoteControl.WebsocketRPC.Enabled {
|
||||
relayWebsocketEvent(result, "orderbook_update", c.AssetType.String(), exchangeName)
|
||||
@@ -497,8 +521,10 @@ func (e *ExchangeCurrencyPairSyncer) Start() {
|
||||
if supportsWebsocket && exchanges[x].IsWebsocketEnabled() {
|
||||
ws, err := exchanges[x].GetWebsocket()
|
||||
if err != nil {
|
||||
log.Errorf(log.SyncMgr, "%s failed to get websocket. Err: %s\n",
|
||||
exchangeName, err)
|
||||
log.Errorf(log.SyncMgr,
|
||||
"%s failed to get websocket. Err: %s\n",
|
||||
exchangeName,
|
||||
err)
|
||||
usingREST = true
|
||||
}
|
||||
|
||||
@@ -507,8 +533,10 @@ func (e *ExchangeCurrencyPairSyncer) Start() {
|
||||
|
||||
err = ws.Connect()
|
||||
if err != nil {
|
||||
log.Errorf(log.SyncMgr, "%s websocket failed to connect. Err: %s\n",
|
||||
exchangeName, err)
|
||||
log.Errorf(log.SyncMgr,
|
||||
"%s websocket failed to connect. Err: %s\n",
|
||||
exchangeName,
|
||||
err)
|
||||
usingREST = true
|
||||
} else {
|
||||
usingWebsocket = true
|
||||
@@ -521,7 +549,22 @@ func (e *ExchangeCurrencyPairSyncer) Start() {
|
||||
}
|
||||
|
||||
for y := range assetTypes {
|
||||
enabledPairs := exchanges[x].GetEnabledPairs(assetTypes[y])
|
||||
if exchanges[x].GetBase().CurrencyPairs.IsAssetEnabled(assetTypes[y]) != nil {
|
||||
log.Warnf(log.SyncMgr,
|
||||
"%s asset type %s is disabled, fetching enabled pairs is paused",
|
||||
exchangeName,
|
||||
assetTypes[y])
|
||||
continue
|
||||
}
|
||||
|
||||
enabledPairs, err := exchanges[x].GetEnabledPairs(assetTypes[y])
|
||||
if err != nil {
|
||||
log.Errorf(log.SyncMgr,
|
||||
"%s failed to get enabled pairs. Err: %s\n",
|
||||
exchangeName,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
for i := range enabledPairs {
|
||||
if e.exists(exchangeName, enabledPairs[i], assetTypes[y]) {
|
||||
continue
|
||||
|
||||
@@ -351,8 +351,14 @@ func wsGetTicker(client *WebsocketClient, data interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := GetSpecificTicker(currency.NewPairFromString(tickerReq.Currency),
|
||||
tickerReq.Exchange, asset.Item(tickerReq.AssetType))
|
||||
p, err := currency.NewPairFromString(tickerReq.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := GetSpecificTicker(p,
|
||||
tickerReq.Exchange,
|
||||
asset.Item(tickerReq.AssetType))
|
||||
|
||||
if err != nil {
|
||||
wsResp.Error = err.Error()
|
||||
@@ -383,7 +389,12 @@ func wsGetOrderbook(client *WebsocketClient, data interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := GetSpecificOrderbook(currency.NewPairFromString(orderbookReq.Currency),
|
||||
p, err := currency.NewPairFromString(orderbookReq.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := GetSpecificOrderbook(p,
|
||||
orderbookReq.Exchange, asset.Item(orderbookReq.AssetType))
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -40,7 +40,6 @@ func (a *Alphapoint) WebsocketClient() {
|
||||
for a.Enabled {
|
||||
msgType, resp, err := a.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
a.Websocket.ReadMessageErrors <- err
|
||||
log.Error(log.ExchangeSys, err)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
@@ -35,10 +34,6 @@ func (a *Alphapoint) SetDefaults() {
|
||||
a.API.CredentialsValidator.RequiresKey = true
|
||||
a.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
a.CurrencyPairs.AssetTypes = asset.Items{
|
||||
asset.Spot,
|
||||
}
|
||||
|
||||
a.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
REST: true,
|
||||
@@ -129,23 +124,24 @@ func (a *Alphapoint) FetchAccountInfo() (account.Holdings, error) {
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerPrice := new(ticker.Price)
|
||||
tick, err := a.GetTicker(p.String())
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tickerPrice.Pair = p
|
||||
tickerPrice.Ask = tick.Ask
|
||||
tickerPrice.Bid = tick.Bid
|
||||
tickerPrice.Low = tick.Low
|
||||
tickerPrice.High = tick.High
|
||||
tickerPrice.Volume = tick.Volume
|
||||
tickerPrice.Last = tick.Last
|
||||
|
||||
err = ticker.ProcessTicker(a.Name, tickerPrice, assetType)
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Pair: p,
|
||||
Ask: tick.Ask,
|
||||
Bid: tick.Bid,
|
||||
Low: tick.Low,
|
||||
High: tick.High,
|
||||
Volume: tick.Volume,
|
||||
Last: tick.Last,
|
||||
ExchangeName: a.Name,
|
||||
AssetType: assetType,
|
||||
})
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ticker.GetTicker(a.Name, p, assetType)
|
||||
@@ -313,11 +309,6 @@ func (a *Alphapoint) WithdrawFiatFundsToInternationalBank(withdrawRequest *withd
|
||||
return "", common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (a *Alphapoint) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (a *Alphapoint) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
return 0, common.ErrFunctionNotSupported
|
||||
@@ -406,28 +397,6 @@ func (a *Alphapoint) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (a *Alphapoint) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (a *Alphapoint) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (a *Alphapoint) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (a *Alphapoint) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (a *Alphapoint) ValidateCredentials() error {
|
||||
|
||||
@@ -14,6 +14,7 @@ type Items []Item
|
||||
const (
|
||||
Spot = Item("spot")
|
||||
Margin = Item("margin")
|
||||
MarginFunding = Item("marginfunding")
|
||||
Index = Item("index")
|
||||
Binary = Item("binary")
|
||||
PerpetualContract = Item("perpetualcontract")
|
||||
@@ -26,6 +27,7 @@ const (
|
||||
var supported = Items{
|
||||
Spot,
|
||||
Margin,
|
||||
MarginFunding,
|
||||
Index,
|
||||
Binary,
|
||||
PerpetualContract,
|
||||
@@ -66,7 +68,6 @@ func (a Items) Contains(i Item) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"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/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
@@ -63,7 +61,6 @@ const (
|
||||
// Binance is the overarching type across the Bithumb package
|
||||
type Binance struct {
|
||||
exchange.Base
|
||||
WebsocketConn *wshandler.WebsocketConnection
|
||||
|
||||
// Valid string list that is required by the exchange
|
||||
validLimits []int
|
||||
@@ -569,17 +566,6 @@ func (b *Binance) CheckLimit(limit int) error {
|
||||
return errors.New("incorrect limit values - valid values are 5, 10, 20, 50, 100, 500, 1000")
|
||||
}
|
||||
|
||||
// CheckSymbol checks value against a variable list
|
||||
func (b *Binance) CheckSymbol(symbol string, assetType asset.Item) error {
|
||||
enPairs := b.GetAvailablePairs(assetType)
|
||||
for x := range enPairs {
|
||||
if b.FormatExchangeCurrency(enPairs[x], assetType).String() == symbol {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("incorrect symbol values - please check available pairs in configuration")
|
||||
}
|
||||
|
||||
// SetValues sets the default valid values
|
||||
func (b *Binance) SetValues() {
|
||||
b.validLimits = []int{5, 10, 20, 50, 100, 500, 1000, 5000}
|
||||
|
||||
@@ -29,6 +29,7 @@ func TestMain(m *testing.M) {
|
||||
binanceConfig.API.Credentials.Key = apiKey
|
||||
binanceConfig.API.Credentials.Secret = apiSecret
|
||||
b.SetDefaults()
|
||||
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = b.Setup(binanceConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Binance setup error", err)
|
||||
|
||||
@@ -33,6 +33,7 @@ func TestMain(m *testing.M) {
|
||||
binanceConfig.API.Credentials.Key = apiKey
|
||||
binanceConfig.API.Credentials.Secret = apiSecret
|
||||
b.SetDefaults()
|
||||
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = b.Setup(binanceConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Binance setup error", err)
|
||||
@@ -45,7 +46,6 @@ func TestMain(m *testing.M) {
|
||||
|
||||
b.HTTPClient = newClient
|
||||
b.API.Endpoints.URL = serverDetails
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
log.Printf(sharedtestvalues.MockTesting, b.Name, b.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
|
||||
|
||||
var feeBuilder = setFeeBuilder()
|
||||
b.GetFeeByType(feeBuilder)
|
||||
if !areTestAPIKeysSet() {
|
||||
if !areTestAPIKeysSet() || mockTests {
|
||||
if feeBuilder.FeeType != exchange.OfflineTradeFee {
|
||||
t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType)
|
||||
}
|
||||
@@ -439,6 +439,7 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
Pair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
@@ -904,10 +905,13 @@ func TestExecutionTypeToOrderStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
currencyPair, err := currency.NewPairFromString("BTCUSDT")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startTime := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.OneDay)
|
||||
_, err = b.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -919,10 +923,13 @@ func TestGetHistoricCandles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSDT")
|
||||
currencyPair, err := currency.NewPairFromString("BTCUSDT")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startTime := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
_, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.OneDay)
|
||||
_, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -25,15 +25,19 @@ type ExchangeInfo struct {
|
||||
} `json:"rateLimits"`
|
||||
ExchangeFilters interface{} `json:"exchangeFilters"`
|
||||
Symbols []struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Status string `json:"status"`
|
||||
BaseAsset string `json:"baseAsset"`
|
||||
BaseAssetPrecision int `json:"baseAssetPrecision"`
|
||||
QuoteAsset string `json:"quoteAsset"`
|
||||
QuotePrecision int `json:"quotePrecision"`
|
||||
OrderTypes []string `json:"orderTypes"`
|
||||
IcebergAllowed bool `json:"icebergAllowed"`
|
||||
Filters []struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Status string `json:"status"`
|
||||
BaseAsset string `json:"baseAsset"`
|
||||
BaseAssetPrecision int `json:"baseAssetPrecision"`
|
||||
QuoteAsset string `json:"quoteAsset"`
|
||||
QuotePrecision int `json:"quotePrecision"`
|
||||
OrderTypes []string `json:"orderTypes"`
|
||||
IcebergAllowed bool `json:"icebergAllowed"`
|
||||
OCOAllowed bool `json:"ocoAllowed"`
|
||||
QuoteOrderQtyMarketAllowed bool `json:"quoteOrderQtyMarketAllowed"`
|
||||
IsSpotTradingAllowed bool `json:"isSpotTradingAllowed"`
|
||||
IsMarginTradingAllowed bool `json:"isMarginTradingAllowed"`
|
||||
Filters []struct {
|
||||
FilterType string `json:"filterType"`
|
||||
MinPrice float64 `json:"minPrice,string"`
|
||||
MaxPrice float64 `json:"maxPrice,string"`
|
||||
@@ -594,89 +598,111 @@ type UserAccountStream struct {
|
||||
}
|
||||
|
||||
type wsAccountInfo struct {
|
||||
CanDeposit bool `json:"D"`
|
||||
CanTrade bool `json:"T"`
|
||||
CanWithdraw bool `json:"W"`
|
||||
EventTime int64 `json:"E"`
|
||||
LastUpdated int64 `json:"u"`
|
||||
BuyerCommission float64 `json:"b"`
|
||||
MakerCommission float64 `json:"m"`
|
||||
SellerCommission float64 `json:"s"`
|
||||
TakerCommission float64 `json:"t"`
|
||||
EventType string `json:"e"`
|
||||
Currencies []struct {
|
||||
Asset string `json:"a"`
|
||||
Available float64 `json:"f,string"`
|
||||
Locked float64 `json:"l,string"`
|
||||
} `json:"B"`
|
||||
Stream string `json:"stream"`
|
||||
Data struct {
|
||||
CanDeposit bool `json:"D"`
|
||||
CanTrade bool `json:"T"`
|
||||
CanWithdraw bool `json:"W"`
|
||||
EventTime int64 `json:"E"`
|
||||
LastUpdated int64 `json:"u"`
|
||||
BuyerCommission float64 `json:"b"`
|
||||
MakerCommission float64 `json:"m"`
|
||||
SellerCommission float64 `json:"s"`
|
||||
TakerCommission float64 `json:"t"`
|
||||
EventType string `json:"e"`
|
||||
Currencies []struct {
|
||||
Asset string `json:"a"`
|
||||
Available float64 `json:"f,string"`
|
||||
Locked float64 `json:"l,string"`
|
||||
} `json:"B"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type wsAccountPosition struct {
|
||||
Currencies []struct {
|
||||
Asset string `json:"a"`
|
||||
Available float64 `json:"f,string"`
|
||||
Locked float64 `json:"l,string"`
|
||||
} `json:"B"`
|
||||
EventTime int64 `json:"E"`
|
||||
LastUpdated int64 `json:"u"`
|
||||
EventType string `json:"e"`
|
||||
Stream string `json:"stream"`
|
||||
Data struct {
|
||||
Currencies []struct {
|
||||
Asset string `json:"a"`
|
||||
Available float64 `json:"f,string"`
|
||||
Locked float64 `json:"l,string"`
|
||||
} `json:"B"`
|
||||
EventTime int64 `json:"E"`
|
||||
LastUpdated int64 `json:"u"`
|
||||
EventType string `json:"e"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type wsBalanceUpdate struct {
|
||||
EventTime int64 `json:"E"`
|
||||
ClearTime int64 `json:"T"`
|
||||
BalanceDelta float64 `json:"d,string"`
|
||||
Asset string `json:"a"`
|
||||
EventType string `json:"e"`
|
||||
Stream string `json:"stream"`
|
||||
Data struct {
|
||||
EventTime int64 `json:"E"`
|
||||
ClearTime int64 `json:"T"`
|
||||
BalanceDelta float64 `json:"d,string"`
|
||||
Asset string `json:"a"`
|
||||
EventType string `json:"e"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type wsOrderUpdate struct {
|
||||
ClientOrderID string `json:"C"`
|
||||
EventTime int64 `json:"E"`
|
||||
IcebergQuantity float64 `json:"F,string"`
|
||||
LastExecutedPrice float64 `json:"L,string"`
|
||||
CommissionAsset float64 `json:"N"`
|
||||
OrderCreationTime int64 `json:"O"`
|
||||
StopPrice float64 `json:"P,string"`
|
||||
QuoteOrderQuantity float64 `json:"Q,string"`
|
||||
Side string `json:"S"`
|
||||
TransactionTime int64 `json:"T"`
|
||||
OrderStatus string `json:"X"`
|
||||
LastQuoteAssetTransactedQuantity float64 `json:"Y,string"`
|
||||
CumulativeQuoteTransactedQuantity float64 `json:"Z,string"`
|
||||
CancelledClientOrderID string `json:"c"`
|
||||
EventType string `json:"e"`
|
||||
TimeInForce string `json:"f"`
|
||||
OrderListID int64 `json:"g"`
|
||||
OrderID int64 `json:"i"`
|
||||
LastExecutedQuantity float64 `json:"l,string"`
|
||||
IsMaker bool `json:"m"`
|
||||
Commission float64 `json:"n,string"`
|
||||
OrderType string `json:"o"`
|
||||
Price float64 `json:"p,string"`
|
||||
Quantity float64 `json:"q,string"`
|
||||
RejectionReason string `json:"r"`
|
||||
Symbol string `json:"s"`
|
||||
TradeID int64 `json:"t"`
|
||||
IsOnOrderBook bool `json:"w"`
|
||||
CurrentExecutionType string `json:"x"`
|
||||
CumulativeFilledQuantity float64 `json:"z,string"`
|
||||
Stream string `json:"stream"`
|
||||
Data struct {
|
||||
ClientOrderID string `json:"C"`
|
||||
EventTime int64 `json:"E"`
|
||||
IcebergQuantity float64 `json:"F,string"`
|
||||
LastExecutedPrice float64 `json:"L,string"`
|
||||
CommissionAsset float64 `json:"N"`
|
||||
OrderCreationTime int64 `json:"O"`
|
||||
StopPrice float64 `json:"P,string"`
|
||||
QuoteOrderQuantity float64 `json:"Q,string"`
|
||||
Side string `json:"S"`
|
||||
TransactionTime int64 `json:"T"`
|
||||
OrderStatus string `json:"X"`
|
||||
LastQuoteAssetTransactedQuantity float64 `json:"Y,string"`
|
||||
CumulativeQuoteTransactedQuantity float64 `json:"Z,string"`
|
||||
CancelledClientOrderID string `json:"c"`
|
||||
EventType string `json:"e"`
|
||||
TimeInForce string `json:"f"`
|
||||
OrderListID int64 `json:"g"`
|
||||
OrderID int64 `json:"i"`
|
||||
LastExecutedQuantity float64 `json:"l,string"`
|
||||
IsMaker bool `json:"m"`
|
||||
Commission float64 `json:"n,string"`
|
||||
OrderType string `json:"o"`
|
||||
Price float64 `json:"p,string"`
|
||||
Quantity float64 `json:"q,string"`
|
||||
RejectionReason string `json:"r"`
|
||||
Symbol string `json:"s"`
|
||||
TradeID int64 `json:"t"`
|
||||
IsOnOrderBook bool `json:"w"`
|
||||
CurrentExecutionType string `json:"x"`
|
||||
CumulativeFilledQuantity float64 `json:"z,string"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type wsListStauts struct {
|
||||
ListClientOrderID string `json:"C"`
|
||||
EventTime int64 `json:"E"`
|
||||
ListOrderStatus string `json:"L"`
|
||||
Orders []struct {
|
||||
ClientOrderID string `json:"c"`
|
||||
OrderID int64 `json:"i"`
|
||||
Symbol string `json:"s"`
|
||||
} `json:"O"`
|
||||
TransactionTime int64 `json:"T"`
|
||||
ContingencyType string `json:"c"`
|
||||
EventType string `json:"e"`
|
||||
OrderListID int64 `json:"g"`
|
||||
ListStatusType string `json:"l"`
|
||||
RejectionReason string `json:"r"`
|
||||
Symbol string `json:"s"`
|
||||
type wsListStatus struct {
|
||||
Stream string `json:"stream"`
|
||||
Data struct {
|
||||
ListClientOrderID string `json:"C"`
|
||||
EventTime int64 `json:"E"`
|
||||
ListOrderStatus string `json:"L"`
|
||||
Orders []struct {
|
||||
ClientOrderID string `json:"c"`
|
||||
OrderID int64 `json:"i"`
|
||||
Symbol string `json:"s"`
|
||||
} `json:"O"`
|
||||
TransactionTime int64 `json:"T"`
|
||||
ContingencyType string `json:"c"`
|
||||
EventType string `json:"e"`
|
||||
OrderListID int64 `json:"g"`
|
||||
ListStatusType string `json:"l"`
|
||||
RejectionReason string `json:"r"`
|
||||
Symbol string `json:"s"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// WsPayload defines the payload through the websocket connection
|
||||
type WsPayload struct {
|
||||
Method string `json:"method"`
|
||||
Params []string `json:"params"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
@@ -14,23 +14,23 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
binanceDefaultWebsocketURL = "wss://stream.binance.com:9443"
|
||||
binanceDefaultWebsocketURL = "wss://stream.binance.com:9443/stream"
|
||||
pingDelay = time.Minute * 9
|
||||
)
|
||||
|
||||
var listenKey string
|
||||
|
||||
// WsConnect intiates a websocket connection
|
||||
// WsConnect initiates a websocket connection
|
||||
func (b *Binance) WsConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
return errors.New(stream.WebsocketNotEnabled)
|
||||
}
|
||||
|
||||
var dialer websocket.Dialer
|
||||
@@ -39,54 +39,43 @@ func (b *Binance) WsConnect() error {
|
||||
listenKey, err = b.GetWsAuthStreamKey()
|
||||
if err != nil {
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
log.Errorf(log.ExchangeSys, "%v unable to connect to authenticated Websocket. Error: %s", b.Name, err)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%v unable to connect to authenticated Websocket. Error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
} else {
|
||||
// cleans on failed connection
|
||||
clean := strings.Split(b.Websocket.GetWebsocketURL(), "?streams=")
|
||||
authPayload := clean[0] + "?streams=" + listenKey
|
||||
err = b.Websocket.SetWebsocketURL(authPayload, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pairs := b.GetEnabledPairs(asset.Spot).Strings()
|
||||
tick := strings.ToLower(
|
||||
strings.Replace(
|
||||
strings.Join(pairs, "@ticker/"), "-", "", -1)) + "@ticker"
|
||||
trade := strings.ToLower(
|
||||
strings.Replace(
|
||||
strings.Join(pairs, "@trade/"), "-", "", -1)) + "@trade"
|
||||
kline := strings.ToLower(
|
||||
strings.Replace(
|
||||
strings.Join(pairs, "@kline_1m/"), "-", "", -1)) + "@kline_1m"
|
||||
depth := strings.ToLower(
|
||||
strings.Replace(
|
||||
strings.Join(pairs, "@depth/"), "-", "", -1)) + "@depth"
|
||||
|
||||
wsurl := b.Websocket.GetWebsocketURL() +
|
||||
"/stream?streams=" +
|
||||
tick +
|
||||
"/" +
|
||||
trade +
|
||||
"/" +
|
||||
kline +
|
||||
"/" +
|
||||
depth
|
||||
if listenKey != "" {
|
||||
wsurl += "/" +
|
||||
listenKey
|
||||
}
|
||||
|
||||
b.WebsocketConn.URL = wsurl
|
||||
b.WebsocketConn.Verbose = b.Verbose
|
||||
|
||||
err = b.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
err = b.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Unable to connect to Websocket. Error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{
|
||||
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
go b.KeepAuthKeyAlive()
|
||||
}
|
||||
|
||||
b.Websocket.Conn.SetupPingHandler(stream.PingHandler{
|
||||
UseGorillaHandler: true,
|
||||
MessageType: websocket.PongMessage,
|
||||
Delay: pingDelay,
|
||||
})
|
||||
|
||||
enabledPairs := b.GetEnabledPairs(asset.Spot)
|
||||
enabledPairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range enabledPairs {
|
||||
err = b.SeedLocalCache(enabledPairs[i])
|
||||
if err != nil {
|
||||
@@ -95,17 +84,19 @@ func (b *Binance) WsConnect() error {
|
||||
}
|
||||
|
||||
go b.wsReadData()
|
||||
go b.KeepAuthKeyAlive()
|
||||
return nil
|
||||
|
||||
subs, err := b.GenerateSubscriptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Websocket.SubscribeToChannels(subs)
|
||||
}
|
||||
|
||||
// KeepAuthKeyAlive will continuously send messages to
|
||||
// keep the WS auth key active
|
||||
func (b *Binance) KeepAuthKeyAlive() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
defer b.Websocket.Wg.Done()
|
||||
ticks := time.NewTicker(time.Minute * 30)
|
||||
for {
|
||||
select {
|
||||
@@ -116,7 +107,8 @@ func (b *Binance) KeepAuthKeyAlive() {
|
||||
err := b.MaintainWsAuthStreamKey()
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
log.Warnf(log.ExchangeSys, b.Name+" - Unable to renew auth websocket token, may experience shutdown")
|
||||
log.Warnf(log.ExchangeSys,
|
||||
b.Name+" - Unable to renew auth websocket token, may experience shutdown")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,25 +117,16 @@ func (b *Binance) KeepAuthKeyAlive() {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *Binance) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
return
|
||||
defer b.Websocket.Wg.Done()
|
||||
|
||||
default:
|
||||
resp, err := b.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
b.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
for {
|
||||
resp := b.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
}
|
||||
err := b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,112 +146,122 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if e, ok := multiStreamData["e"].(string); ok {
|
||||
switch e {
|
||||
case "outboundAccountInfo":
|
||||
var data wsAccountInfo
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to outboundAccountInfo structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
case "outboundAccountPosition":
|
||||
var data wsAccountPosition
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to outboundAccountPosition structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
case "balanceUpdate":
|
||||
var data wsBalanceUpdate
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to balanceUpdate structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
case "executionReport":
|
||||
var data wsOrderUpdate
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to executionReport structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
var orderID = strconv.FormatInt(data.OrderID, 10)
|
||||
oType, err := order.StringToOrderType(data.OrderType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
if newdata, ok := multiStreamData["data"].(map[string]interface{}); ok {
|
||||
if e, ok := newdata["e"].(string); ok {
|
||||
switch e {
|
||||
case "outboundAccountInfo":
|
||||
var data wsAccountInfo
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to outboundAccountInfo structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
var oSide order.Side
|
||||
oSide, err = order.StringToOrderSide(data.Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
b.Websocket.DataHandler <- data
|
||||
case "outboundAccountPosition":
|
||||
var data wsAccountPosition
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to outboundAccountPosition structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = stringToOrderStatus(data.CurrentExecutionType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
b.Websocket.DataHandler <- data
|
||||
case "balanceUpdate":
|
||||
var data wsBalanceUpdate
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to balanceUpdate structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
case "executionReport":
|
||||
var data wsOrderUpdate
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to executionReport structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
var orderID = strconv.FormatInt(data.Data.OrderID, 10)
|
||||
oType, err := order.StringToOrderType(data.Data.OrderType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oSide order.Side
|
||||
oSide, err = order.StringToOrderSide(data.Data.Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = stringToOrderStatus(data.Data.CurrentExecutionType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = b.GetRequestFormattedPairAndAssetType(data.Data.Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: data.Data.Price,
|
||||
Amount: data.Data.Quantity,
|
||||
ExecutedAmount: data.Data.CumulativeFilledQuantity,
|
||||
RemainingAmount: data.Data.Quantity - data.Data.CumulativeFilledQuantity,
|
||||
Exchange: b.Name,
|
||||
ID: orderID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: time.Unix(0, data.Data.OrderCreationTime*int64(time.Millisecond)),
|
||||
Pair: p,
|
||||
}
|
||||
case "listStatus":
|
||||
var data wsListStatus
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to listStatus structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
}
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = b.GetRequestFormattedPairAndAssetType(data.Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: data.Price,
|
||||
Amount: data.Quantity,
|
||||
ExecutedAmount: data.CumulativeFilledQuantity,
|
||||
RemainingAmount: data.Quantity - data.CumulativeFilledQuantity,
|
||||
Exchange: b.Name,
|
||||
ID: orderID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: time.Unix(0, data.OrderCreationTime*int64(time.Millisecond)),
|
||||
Pair: p,
|
||||
}
|
||||
case "listStatus":
|
||||
var data wsListStauts
|
||||
err := json.Unmarshal(respRaw, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - Could not convert to listStatus structure %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- data
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if stream, ok := multiStreamData["stream"].(string); ok {
|
||||
streamType := strings.Split(stream, "@")
|
||||
if wsStream, ok := multiStreamData["stream"].(string); ok {
|
||||
streamType := strings.Split(wsStream, "@")
|
||||
if len(streamType) > 1 {
|
||||
if data, ok := multiStreamData["data"]; ok {
|
||||
rawData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch streamType[1] {
|
||||
case "trade":
|
||||
var trade TradeStream
|
||||
@@ -293,14 +286,18 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
err)
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
CurrencyPair: currency.NewPairFromFormattedPairs(trade.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true)),
|
||||
Timestamp: time.Unix(0, trade.TimeStamp*int64(time.Millisecond)),
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
pair, err := currency.NewPairFromFormattedPairs(trade.Symbol, pairs, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- stream.TradeData{
|
||||
CurrencyPair: pair,
|
||||
Timestamp: time.Unix(0, trade.TimeStamp*int64(time.Millisecond)),
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
case "ticker":
|
||||
var t TickerStream
|
||||
@@ -311,6 +308,11 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
err.Error())
|
||||
}
|
||||
|
||||
pair, err := currency.NewPairFromFormattedPairs(t.Symbol, pairs, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: b.Name,
|
||||
Open: t.OpenPrice,
|
||||
@@ -324,8 +326,7 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
Last: t.LastPrice,
|
||||
LastUpdated: time.Unix(0, t.EventTime*int64(time.Millisecond)),
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromFormattedPairs(t.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true)),
|
||||
Pair: pair,
|
||||
}
|
||||
case "kline_1m", "kline_3m", "kline_5m", "kline_15m", "kline_30m", "kline_1h", "kline_2h", "kline_4h",
|
||||
"kline_6h", "kline_8h", "kline_12h", "kline_1d", "kline_3d", "kline_1w", "kline_1M":
|
||||
@@ -337,10 +338,14 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
err)
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(0, kline.EventTime*int64(time.Millisecond)),
|
||||
Pair: currency.NewPairFromFormattedPairs(kline.Symbol, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true)),
|
||||
pair, err := currency.NewPairFromFormattedPairs(kline.Symbol, pairs, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- stream.KlineData{
|
||||
Timestamp: time.Unix(0, kline.EventTime*int64(time.Millisecond)),
|
||||
Pair: pair,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
StartTime: time.Unix(0, kline.Kline.StartTime*int64(time.Millisecond)),
|
||||
@@ -361,22 +366,16 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
err)
|
||||
}
|
||||
|
||||
err = b.UpdateLocalCache(&depth)
|
||||
err = b.UpdateLocalBuffer(&depth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v - UpdateLocalCache error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
currencyPair := currency.NewPairFromFormattedPairs(depth.Pair, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true))
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
b.Websocket.DataHandler <- stream.UnhandledMessageWarning{
|
||||
Message: b.Name + stream.UnhandledMessage + string(respRaw),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,11 +402,15 @@ func stringToOrderStatus(status string) (order.Status, error) {
|
||||
|
||||
// SeedLocalCache seeds depth data
|
||||
func (b *Binance) SeedLocalCache(p currency.Pair) error {
|
||||
ob, err := b.GetOrderBook(
|
||||
OrderBookDataRequestParams{
|
||||
Symbol: b.FormatExchangeCurrency(p, asset.Spot).String(),
|
||||
Limit: 1000,
|
||||
})
|
||||
fPair, err := b.FormatExchangeCurrency(p, asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ob, err := b.GetOrderBook(OrderBookDataRequestParams{
|
||||
Symbol: fPair.String(),
|
||||
Limit: 1000,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -439,11 +442,34 @@ func (b *Binance) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *OrderBoo
|
||||
return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
}
|
||||
|
||||
// UpdateLocalCache updates and returns the most recent iteration of the orderbook
|
||||
func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error {
|
||||
currencyPair := currency.NewPairFromFormattedPairs(wsdp.Pair, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true))
|
||||
// UpdateLocalBuffer updates and returns the most recent iteration of the orderbook
|
||||
func (b *Binance) UpdateLocalBuffer(wsdp *WebsocketDepthStream) error {
|
||||
enabledPairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currencyPair, err := currency.NewPairFromFormattedPairs(wsdp.Pair,
|
||||
enabledPairs,
|
||||
format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentBook := b.Websocket.Orderbook.GetOrderbook(currencyPair, asset.Spot)
|
||||
if currentBook == nil {
|
||||
// Used when a pair/s is enabled while connected
|
||||
err = b.SeedLocalCache(currencyPair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentBook = b.Websocket.Orderbook.GetOrderbook(currencyPair, asset.Spot)
|
||||
}
|
||||
|
||||
// Drop any event where u is <= lastUpdateId in the snapshot.
|
||||
// The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1.
|
||||
@@ -479,7 +505,7 @@ func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error {
|
||||
updateAsk = append(updateAsk, orderbook.Item{Price: p, Amount: a})
|
||||
}
|
||||
|
||||
return b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
return b.Websocket.Orderbook.Update(&buffer.Update{
|
||||
Bids: updateBid,
|
||||
Asks: updateAsk,
|
||||
Pair: currencyPair,
|
||||
@@ -487,3 +513,62 @@ func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error {
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateSubscriptions generates the default subscription set
|
||||
func (b *Binance) GenerateSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var channels = []string{"@ticker", "@trade", "@kline_1m", "@depth@100ms"}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
assets := b.GetAssetTypes()
|
||||
for x := range assets {
|
||||
pairs, err := b.GetEnabledPairs(assets[x])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for y := range pairs {
|
||||
for z := range channels {
|
||||
lp := pairs[y].Lower()
|
||||
lp.Delimiter = ""
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: lp.String() + channels[z],
|
||||
Currency: pairs[y],
|
||||
Asset: assets[x],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a set of channels
|
||||
func (b *Binance) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
payload := WsPayload{
|
||||
Method: "SUBSCRIBE",
|
||||
}
|
||||
|
||||
for i := range channelsToSubscribe {
|
||||
payload.Params = append(payload.Params, channelsToSubscribe[i].Channel)
|
||||
}
|
||||
err := b.Websocket.Conn.SendJSONMessage(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribes from a set of channels
|
||||
func (b *Binance) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
payload := WsPayload{
|
||||
Method: "UNSUBSCRIBE",
|
||||
}
|
||||
for i := range channelsToUnsubscribe {
|
||||
payload.Params = append(payload.Params, channelsToUnsubscribe[i].Channel)
|
||||
}
|
||||
err := b.Websocket.Conn.SendJSONMessage(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"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/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -56,21 +56,23 @@ func (b *Binance) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
b.SetValues()
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
fmt1 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Delimiter: "-",
|
||||
Delimiter: currency.DashDelimiter,
|
||||
Uppercase: true,
|
||||
},
|
||||
}
|
||||
|
||||
err := b.StoreAssetPairFormat(asset.Spot, fmt1)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
err = b.StoreAssetPairFormat(asset.Margin, fmt1)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
b.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
REST: true,
|
||||
@@ -145,7 +147,7 @@ func (b *Binance) SetDefaults() {
|
||||
|
||||
b.API.Endpoints.URLDefault = apiURL
|
||||
b.API.Endpoints.URL = b.API.Endpoints.URLDefault
|
||||
b.Websocket = wshandler.New()
|
||||
b.Websocket = stream.New()
|
||||
b.API.Endpoints.WebsocketURL = binanceDefaultWebsocketURL
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
@@ -164,40 +166,31 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Websocket.Setup(
|
||||
&wshandler.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: binanceDefaultWebsocketURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
|
||||
err = b.Websocket.Setup(&stream.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: binanceDefaultWebsocketURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
GenerateSubscriptions: b.GenerateSubscriptions,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit,
|
||||
SortBuffer: true,
|
||||
SortBufferByUpdateIDs: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: b.Websocket.GetWebsocketURL(),
|
||||
ProxyURL: b.Websocket.GetProxyAddress(),
|
||||
Verbose: b.Verbose,
|
||||
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Start starts the Binance go routine
|
||||
@@ -221,30 +214,59 @@ func (b *Binance) Run() {
|
||||
}
|
||||
|
||||
forceUpdate := false
|
||||
delim := b.GetPairFormat(asset.Spot, false).Delimiter
|
||||
if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), delim) ||
|
||||
!common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), delim) {
|
||||
enabledPairs := currency.NewPairsFromStrings(
|
||||
[]string{currency.BTC.String() + delim + currency.USDT.String()},
|
||||
)
|
||||
log.Warn(log.ExchangeSys,
|
||||
"Available pairs for Binance reset due to config upgrade, please enable the ones you would like to use again")
|
||||
forceUpdate = true
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to get enabled currencies. Err %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return
|
||||
}
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to get enabled currencies. Err %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return
|
||||
}
|
||||
|
||||
err := b.UpdatePairs(enabledPairs, asset.Spot, true, true)
|
||||
avail, err := b.GetAvailablePairs(asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to get available currencies. Err %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return
|
||||
}
|
||||
|
||||
if !common.StringDataContains(pairs.Strings(), format.Delimiter) ||
|
||||
!common.StringDataContains(avail.Strings(), format.Delimiter) {
|
||||
var enabledPairs currency.Pairs
|
||||
enabledPairs, err = currency.NewPairsFromStrings([]string{
|
||||
currency.BTC.String() +
|
||||
format.Delimiter +
|
||||
currency.USDT.String()})
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
} else {
|
||||
log.Warn(log.ExchangeSys,
|
||||
"Available pairs for Binance reset due to config upgrade, please enable the ones you would like to use again")
|
||||
forceUpdate = true
|
||||
|
||||
err = b.UpdatePairs(enabledPairs, asset.Spot, true, true)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !b.GetEnabledFeatures().AutoPairUpdates && !forceUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
err := b.UpdateTradablePairs(forceUpdate)
|
||||
err = b.UpdateTradablePairs(forceUpdate)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update tradable pairs. Err: %s",
|
||||
@@ -254,36 +276,55 @@ func (b *Binance) Run() {
|
||||
}
|
||||
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (b *Binance) FetchTradablePairs(asset asset.Item) ([]string, error) {
|
||||
var validCurrencyPairs []string
|
||||
|
||||
func (b *Binance) FetchTradablePairs(a asset.Item) ([]string, error) {
|
||||
info, err := b.GetExchangeInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(a, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pairs []string
|
||||
for x := range info.Symbols {
|
||||
if info.Symbols[x].Status == "TRADING" {
|
||||
validCurrencyPairs = append(validCurrencyPairs, info.Symbols[x].BaseAsset+
|
||||
b.GetPairFormat(asset, false).Delimiter+
|
||||
info.Symbols[x].QuoteAsset)
|
||||
pair := info.Symbols[x].BaseAsset +
|
||||
format.Delimiter +
|
||||
info.Symbols[x].QuoteAsset
|
||||
if a == asset.Spot && info.Symbols[x].IsSpotTradingAllowed {
|
||||
pairs = append(pairs, pair)
|
||||
}
|
||||
if a == asset.Margin && info.Symbols[x].IsMarginTradingAllowed {
|
||||
pairs = append(pairs, pair)
|
||||
}
|
||||
}
|
||||
}
|
||||
return validCurrencyPairs, nil
|
||||
return pairs, nil
|
||||
}
|
||||
|
||||
// UpdateTradablePairs updates the exchanges available pairs and stores
|
||||
// them in the exchanges config
|
||||
func (b *Binance) UpdateTradablePairs(forceUpdate bool) error {
|
||||
pairs, err := b.FetchTradablePairs(asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assetTypes := b.GetAssetTypes()
|
||||
for i := range assetTypes {
|
||||
p, err := b.FetchTradablePairs(assetTypes[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(currency.NewPairsFromStrings(pairs),
|
||||
asset.Spot,
|
||||
false,
|
||||
forceUpdate)
|
||||
pairs, err := currency.NewPairsFromStrings(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(pairs, assetTypes[i], false, forceUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
@@ -292,28 +333,39 @@ func (b *Binance) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.P
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pairs := b.GetEnabledPairs(assetType)
|
||||
|
||||
pairs, err := b.GetEnabledPairs(assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range pairs {
|
||||
for y := range tick {
|
||||
pairFmt := b.FormatExchangeCurrency(pairs[i], assetType).String()
|
||||
if tick[y].Symbol != pairFmt {
|
||||
pairFmt, err := b.FormatExchangeCurrency(pairs[i], assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tick[y].Symbol != pairFmt.String() {
|
||||
continue
|
||||
}
|
||||
tickerPrice := &ticker.Price{
|
||||
Last: tick[y].LastPrice,
|
||||
High: tick[y].HighPrice,
|
||||
Low: tick[y].LowPrice,
|
||||
Bid: tick[y].BidPrice,
|
||||
Ask: tick[y].AskPrice,
|
||||
Volume: tick[y].Volume,
|
||||
QuoteVolume: tick[y].QuoteVolume,
|
||||
Open: tick[y].OpenPrice,
|
||||
Close: tick[y].PrevClosePrice,
|
||||
Pair: pairs[i],
|
||||
}
|
||||
err = ticker.ProcessTicker(b.Name, tickerPrice, assetType)
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Last: tick[y].LastPrice,
|
||||
High: tick[y].HighPrice,
|
||||
Low: tick[y].LowPrice,
|
||||
Bid: tick[y].BidPrice,
|
||||
Ask: tick[y].AskPrice,
|
||||
Volume: tick[y].Volume,
|
||||
QuoteVolume: tick[y].QuoteVolume,
|
||||
Open: tick[y].OpenPrice,
|
||||
Close: tick[y].PrevClosePrice,
|
||||
Pair: pairs[i],
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,13 +392,19 @@ func (b *Binance) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderb
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (b *Binance) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
orderBook := new(orderbook.Base)
|
||||
orderbookNew, err := b.GetOrderBook(OrderBookDataRequestParams{Symbol: b.FormatExchangeCurrency(p,
|
||||
assetType).String(), Limit: 1000})
|
||||
fpair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderbookNew, err := b.GetOrderBook(OrderBookDataRequestParams{
|
||||
Symbol: fpair.String(),
|
||||
Limit: 1000})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBook := new(orderbook.Base)
|
||||
for x := range orderbookNew.Bids {
|
||||
orderBook.Bids = append(orderBook.Bids,
|
||||
orderbook.Item{
|
||||
@@ -499,8 +557,12 @@ func (b *Binance) CancelOrder(order *order.Cancel) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = b.CancelExistingOrder(b.FormatExchangeCurrency(order.Pair,
|
||||
order.AssetType).String(),
|
||||
fpair, err := b.FormatExchangeCurrency(order.Pair, order.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = b.CancelExistingOrder(fpair.String(),
|
||||
orderIDInt,
|
||||
order.AccountID)
|
||||
return err
|
||||
@@ -567,11 +629,6 @@ func (b *Binance) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *Binance) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return b.Websocket, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (b *Binance) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if (!b.AllowAuthenticatedRequest() || b.SkipAuthCheck) && // Todo check connection status
|
||||
@@ -589,8 +646,13 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
|
||||
var orders []order.Detail
|
||||
for x := range req.Pairs {
|
||||
resp, err := b.OpenOrders(b.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot).String())
|
||||
fpair, err := b.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := b.OpenOrders(fpair.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -600,6 +662,11 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
orderType := order.Type(strings.ToUpper(resp[i].Type))
|
||||
orderDate := time.Unix(0, int64(resp[i].Time)*int64(time.Millisecond))
|
||||
|
||||
pair, err := currency.NewPairFromString(resp[i].Symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].OrigQty,
|
||||
Date: orderDate,
|
||||
@@ -609,7 +676,7 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
Type: orderType,
|
||||
Price: resp[i].Price,
|
||||
Status: order.Status(resp[i].Status),
|
||||
Pair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Pair: pair,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -629,8 +696,11 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
|
||||
var orders []order.Detail
|
||||
for x := range req.Pairs {
|
||||
resp, err := b.AllOrders(b.FormatExchangeCurrency(req.Pairs[x],
|
||||
asset.Spot).String(),
|
||||
fpair, err := b.FormatExchangeCurrency(req.Pairs[x], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := b.AllOrders(fpair.String(),
|
||||
"",
|
||||
"1000")
|
||||
if err != nil {
|
||||
@@ -646,6 +716,11 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
continue
|
||||
}
|
||||
|
||||
pair, err := currency.NewPairFromString(resp[i].Symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].OrigQty,
|
||||
Date: orderDate,
|
||||
@@ -654,7 +729,7 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
Side: orderSide,
|
||||
Type: orderType,
|
||||
Price: resp[i].Price,
|
||||
Pair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Pair: pair,
|
||||
Status: order.Status(resp[i].Status),
|
||||
})
|
||||
}
|
||||
@@ -666,28 +741,6 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *Binance) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *Binance) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *Binance) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return b.Websocket.GetSubscriptions(), nil
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *Binance) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (b *Binance) ValidateCredentials() error {
|
||||
@@ -718,9 +771,13 @@ func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, en
|
||||
return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits)
|
||||
}
|
||||
|
||||
fpair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
req := KlinesRequestParams{
|
||||
Interval: b.FormatExchangeKlineInterval(interval),
|
||||
Symbol: b.FormatExchangeCurrency(pair, a).String(),
|
||||
Symbol: fpair.String(),
|
||||
StartTime: start.Unix() * 1000,
|
||||
EndTime: end.Unix() * 1000,
|
||||
Limit: int(b.Features.Enabled.Kline.ResultLimit),
|
||||
@@ -768,11 +825,16 @@ func (b *Binance) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, s
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
formattedPair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
for x := range dates {
|
||||
req := KlinesRequestParams{
|
||||
Interval: b.FormatExchangeKlineInterval(interval),
|
||||
Symbol: b.FormatExchangeCurrency(pair, a).String(),
|
||||
Symbol: formattedPair.String(),
|
||||
StartTime: dates[x].Start.UTC().Unix() * 1000,
|
||||
EndTime: dates[x].End.UTC().Unix() * 1000,
|
||||
Limit: int(b.Features.Enabled.Kline.ResultLimit),
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -83,9 +82,7 @@ const (
|
||||
// Bitfinex is the overarching type across the bitfinex package
|
||||
type Bitfinex struct {
|
||||
exchange.Base
|
||||
WebsocketConn *wshandler.WebsocketConnection
|
||||
AuthenticatedWebsocketConn *wshandler.WebsocketConnection
|
||||
WebsocketSubdChannels map[int]WebsocketChanInfo
|
||||
WebsocketSubdChannels map[int]WebsocketChanInfo
|
||||
}
|
||||
|
||||
// GetPlatformStatus returns the Bifinex platform status
|
||||
@@ -778,7 +775,7 @@ func (b *Bitfinex) WithdrawFIAT(withdrawalType, walletType string, withdrawReque
|
||||
// Major Upgrade needed on this function to include all query params
|
||||
func (b *Bitfinex) NewOrder(currencyPair, orderType string, amount, price float64, buy, hidden bool) (Order, error) {
|
||||
if !common.StringDataCompare(AcceptedOrderType, orderType) {
|
||||
return Order{}, errors.New("order type not accepted")
|
||||
return Order{}, fmt.Errorf("order type %s not accepted", orderType)
|
||||
}
|
||||
|
||||
response := Order{}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -42,6 +41,7 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
log.Fatal("Bitfinex Setup() init error")
|
||||
}
|
||||
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = b.Setup(bfxConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Bitfinex setup error", err)
|
||||
@@ -58,11 +58,32 @@ func TestMain(m *testing.M) {
|
||||
b.API.AuthenticatedWebsocketSupport = true
|
||||
}
|
||||
b.WebsocketSubdChannels = make(map[int]WebsocketChanInfo)
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestAppendOptionalDelimiter(t *testing.T) {
|
||||
t.Parallel()
|
||||
curr1, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.appendOptionalDelimiter(&curr1)
|
||||
if curr1.Delimiter != "" {
|
||||
t.Errorf("Expected no delimiter, received %v", curr1.Delimiter)
|
||||
}
|
||||
curr2, err := currency.NewPairFromString("DUSK:USD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curr2.Delimiter = ""
|
||||
b.appendOptionalDelimiter(&curr2)
|
||||
if curr2.Delimiter != ":" {
|
||||
t.Errorf("Expected \":\" as a delimiter, received %v", curr2.Delimiter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPlatformStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
result, err := b.GetPlatformStatus()
|
||||
@@ -320,7 +341,7 @@ func TestNewOrder(t *testing.T) {
|
||||
|
||||
_, err := b.NewOrder("BTCUSD",
|
||||
order.Limit.Lower(),
|
||||
1,
|
||||
-1,
|
||||
2,
|
||||
false,
|
||||
true)
|
||||
@@ -330,27 +351,17 @@ func TestNewOrder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateTicker(t *testing.T) {
|
||||
_, err := b.UpdateTicker(currency.NewPairFromString("BTCUSD"), asset.Spot)
|
||||
pair, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.UpdateTicker(pair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendOptionalDelimiter(t *testing.T) {
|
||||
t.Parallel()
|
||||
curr1 := currency.NewPairFromString("BTCUSD")
|
||||
b.appendOptionalDelimiter(&curr1)
|
||||
if curr1.Delimiter != "" {
|
||||
t.Errorf("Expected no delimiter, received %v", curr1.Delimiter)
|
||||
}
|
||||
curr2 := currency.NewPairFromString("DUSK:USD")
|
||||
curr2.Delimiter = ""
|
||||
b.appendOptionalDelimiter(&curr2)
|
||||
if curr2.Delimiter != ":" {
|
||||
t.Errorf("Expected \"-\" as a delimiter, received %v", curr2.Delimiter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOrderMulti(t *testing.T) {
|
||||
if !b.ValidateAPICredentials() {
|
||||
t.SkipNow()
|
||||
@@ -762,19 +773,20 @@ func TestSubmitOrder(t *testing.T) {
|
||||
var orderSubmission = &order.Submit{
|
||||
Pair: currency.Pair{
|
||||
Delimiter: "_",
|
||||
Base: currency.BTC,
|
||||
Base: currency.XRP,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
AssetType: asset.Spot,
|
||||
Side: order.Sell,
|
||||
Type: order.Limit,
|
||||
Price: 1000,
|
||||
Amount: 20,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := b.SubmitOrder(orderSubmission)
|
||||
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
t.Errorf("Could not cancel orders: %v", err)
|
||||
t.Errorf("Could not place order: %v", err)
|
||||
}
|
||||
if areTestAPIKeysSet() && !response.IsOrderPlaced {
|
||||
t.Error("Order not placed")
|
||||
@@ -944,21 +956,12 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
}
|
||||
|
||||
func setupWs() {
|
||||
b.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: authenticatedBitfinexWebsocketEndpoint,
|
||||
Verbose: b.Verbose,
|
||||
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
|
||||
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := b.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{})
|
||||
err := b.Websocket.AuthConn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go b.wsReadData(b.AuthenticatedWebsocketConn)
|
||||
go b.wsReadData(b.Websocket.AuthConn)
|
||||
go b.WsDataHandler()
|
||||
}
|
||||
|
||||
@@ -1001,12 +1004,13 @@ func TestWsPlaceOrder(t *testing.T) {
|
||||
if !wsAuthExecuted {
|
||||
runAuth(t)
|
||||
}
|
||||
|
||||
_, err := b.WsNewOrder(&WsNewOrderRequest{
|
||||
CustomID: 1337,
|
||||
Type: order.Buy.String(),
|
||||
Symbol: "tBTCUSD",
|
||||
Amount: 10,
|
||||
Price: -10,
|
||||
GroupID: 1,
|
||||
Type: "EXCHANGE LIMIT",
|
||||
Symbol: "tXRPUSD",
|
||||
Amount: -20,
|
||||
Price: 1000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -1169,6 +1173,24 @@ func TestWsTickerResponse(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
b.WebsocketSubdChannels[123412] = WebsocketChanInfo{Pair: "XAUTF0:USTF0", Channel: wsTicker}
|
||||
pressXToJSON = `[123412,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]`
|
||||
err = b.wsHandleData([]byte(pressXToJSON))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
b.WebsocketSubdChannels[123413] = WebsocketChanInfo{Pair: "trade:1m:tXRPUSD", Channel: wsTicker}
|
||||
pressXToJSON = `[123413,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]`
|
||||
err = b.wsHandleData([]byte(pressXToJSON))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
b.WebsocketSubdChannels[123414] = WebsocketChanInfo{Pair: "trade:1m:fZRX:p30", Channel: wsTicker}
|
||||
pressXToJSON = `[123414,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]`
|
||||
err = b.wsHandleData([]byte(pressXToJSON))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsCandleResponse(t *testing.T) {
|
||||
@@ -1186,6 +1208,7 @@ func TestWsCandleResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWsOrderSnapshot(t *testing.T) {
|
||||
b.WsAddSubscriptionChannel(0, "account", "N/A")
|
||||
pressXToJSON := `[0,"os",[[34930659963,null,1574955083558,"tETHUSD",1574955083558,1574955083573,0.201104,0.201104,"EXCHANGE LIMIT",null,null,null,0,"ACTIVE",null,null,120,0,0,0,null,null,null,0,0,null,null,null,"BFX",null,null,null]]]`
|
||||
err := b.wsHandleData([]byte(pressXToJSON))
|
||||
if err != nil {
|
||||
@@ -1213,9 +1236,12 @@ func TestWsNotifications(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTCUSD")
|
||||
currencyPair, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
_, err = b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1227,9 +1253,12 @@ func TestGetHistoricCandles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("TBTCUSD")
|
||||
currencyPair, err := currency.NewPairFromString("TBTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour)
|
||||
_, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1241,42 +1270,95 @@ func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFixCasing(t *testing.T) {
|
||||
ret := b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Spot)
|
||||
pair, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, err := b.fixCasing(pair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret != "tBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("TBTCUSD"), asset.Spot)
|
||||
pair, err = currency.NewPairFromString("TBTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, err = b.fixCasing(pair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret != "tBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("tBTCUSD"), asset.Spot)
|
||||
pair, err = currency.NewPairFromString("tBTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, err = b.fixCasing(pair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret != "tBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Margin)
|
||||
pair, err = currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, err = b.fixCasing(pair, asset.Margin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret != "fBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Spot)
|
||||
pair, err = currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, err = b.fixCasing(pair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret != "tBTCUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("FUNETH"), asset.Spot)
|
||||
pair, err = currency.NewPairFromString("FUNETH")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, err = b.fixCasing(pair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret != "tFUNETH" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("TNBUSD"), asset.Spot)
|
||||
pair, err = currency.NewPairFromString("TNBUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, err = b.fixCasing(pair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret != "tTNBUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
ret = b.fixCasing(currency.NewPairFromString("tTNBUSD"), asset.Spot)
|
||||
pair, err = currency.NewPairFromString("tTNBUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ret, err = b.fixCasing(pair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ret != "tTNBUSD" {
|
||||
t.Errorf("unexpected result: %v", ret)
|
||||
}
|
||||
|
||||
@@ -374,9 +374,11 @@ type WebsocketChanInfo struct {
|
||||
|
||||
// WebsocketBook holds booking information
|
||||
type WebsocketBook struct {
|
||||
Price float64
|
||||
ID int64
|
||||
Price float64
|
||||
Amount float64
|
||||
Rate float64
|
||||
Period int64
|
||||
}
|
||||
|
||||
// WebsocketTrade holds trade information
|
||||
|
||||
@@ -10,69 +10,74 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
var comms = make(chan wshandler.WebsocketResponse)
|
||||
var comms = make(chan stream.Response)
|
||||
|
||||
// WsConnect starts a new websocket connection
|
||||
func (b *Bitfinex) WsConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
return errors.New(stream.WebsocketNotEnabled)
|
||||
}
|
||||
|
||||
var dialer websocket.Dialer
|
||||
err := b.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
err := b.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v unable to connect to Websocket. Error: %s", b.Name, err)
|
||||
return fmt.Errorf("%v unable to connect to Websocket. Error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
go b.wsReadData(b.WebsocketConn)
|
||||
go b.wsReadData(b.Websocket.Conn)
|
||||
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
err = b.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{})
|
||||
err = b.Websocket.AuthConn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%v unable to connect to authenticated Websocket. Error: %s", b.Name, err)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%v unable to connect to authenticated Websocket. Error: %s",
|
||||
b.Name,
|
||||
err)
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
go b.wsReadData(b.AuthenticatedWebsocketConn)
|
||||
go b.wsReadData(b.Websocket.AuthConn)
|
||||
err = b.WsSendAuth()
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%v - authentication failed: %v\n",
|
||||
b.Name,
|
||||
err)
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
}
|
||||
|
||||
b.GenerateDefaultSubscriptions()
|
||||
subs, err := b.GenerateDefaultSubscriptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go b.WsDataHandler()
|
||||
return nil
|
||||
return b.Websocket.SubscribeToChannels(subs)
|
||||
}
|
||||
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *Bitfinex) wsReadData(ws *wshandler.WebsocketConnection) {
|
||||
func (b *Bitfinex) wsReadData(ws stream.Connection) {
|
||||
b.Websocket.Wg.Add(1)
|
||||
defer b.Websocket.Wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
resp := ws.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
default:
|
||||
resp, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
b.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
comms <- resp
|
||||
}
|
||||
comms <- resp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +87,6 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
defer b.Websocket.Wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
return
|
||||
case resp := <-comms:
|
||||
if resp.Type == websocket.TextMessage {
|
||||
err := b.wsHandleData(resp.Raw)
|
||||
@@ -91,6 +94,8 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
case <-b.Websocket.ShutdownC:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,12 +111,21 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
event := d["event"]
|
||||
switch event {
|
||||
case "subscribed":
|
||||
if symbol, ok := d["pair"].(string); ok {
|
||||
if symbol, ok := d["symbol"].(string); ok {
|
||||
b.WsAddSubscriptionChannel(int(d["chanId"].(float64)),
|
||||
d["channel"].(string),
|
||||
symbol,
|
||||
)
|
||||
} else if key, ok := d["key"].(string); ok {
|
||||
// Capture trading subscriptions
|
||||
contents := strings.Split(d["key"].(string), ":")
|
||||
if len(contents) > 3 {
|
||||
// Edge case to parse margin strings.
|
||||
// map[chanId:139136 channel:candles event:subscribed key:trade:1m:tXAUTF0:USTF0]
|
||||
if contents[2][0] == 't' {
|
||||
key = contents[2] + ":" + contents[3]
|
||||
}
|
||||
}
|
||||
b.WsAddSubscriptionChannel(int(d["chanId"].(float64)),
|
||||
d["channel"].(string),
|
||||
key,
|
||||
@@ -134,6 +148,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
chanID := int(d[0].(float64))
|
||||
chanInfo, ok := b.WebsocketSubdChannels[chanID]
|
||||
if !ok && chanID != 0 {
|
||||
@@ -141,67 +156,122 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
chanID)
|
||||
}
|
||||
|
||||
var chanAsset = asset.Spot
|
||||
var pair currency.Pair
|
||||
pairInfo := strings.Split(chanInfo.Pair, ":")
|
||||
switch {
|
||||
case len(pairInfo) >= 3:
|
||||
newPair := pairInfo[2]
|
||||
if newPair[0] == 'f' {
|
||||
chanAsset = asset.MarginFunding
|
||||
}
|
||||
|
||||
pair, err = currency.NewPairFromString(newPair[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case len(pairInfo) == 1:
|
||||
newPair := pairInfo[0]
|
||||
if newPair[0] == 'f' {
|
||||
chanAsset = asset.MarginFunding
|
||||
}
|
||||
|
||||
pair, err = currency.NewPairFromString(newPair[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case chanInfo.Pair != "":
|
||||
if strings.Contains(chanInfo.Pair, ":") {
|
||||
chanAsset = asset.Margin
|
||||
}
|
||||
|
||||
pair, err = currency.NewPairFromString(chanInfo.Pair[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch chanInfo.Channel {
|
||||
case wsBook:
|
||||
var newOrderbook []WebsocketBook
|
||||
curr := currency.NewPairFromString(chanInfo.Pair)
|
||||
if obSnapBundle, ok := d[1].([]interface{}); ok {
|
||||
switch id := obSnapBundle[0].(type) {
|
||||
case []interface{}:
|
||||
for i := range obSnapBundle {
|
||||
data := obSnapBundle[i].([]interface{})
|
||||
obSnapBundle, ok := d[1].([]interface{})
|
||||
if !ok {
|
||||
return errors.New("orderbook interface cast failed")
|
||||
}
|
||||
|
||||
switch id := obSnapBundle[0].(type) {
|
||||
case []interface{}:
|
||||
for i := range obSnapBundle {
|
||||
data := obSnapBundle[i].([]interface{})
|
||||
if len(data) == 4 {
|
||||
newOrderbook = append(newOrderbook, WebsocketBook{
|
||||
ID: int64(data[0].(float64)),
|
||||
Period: int64(data[1].(float64)),
|
||||
Rate: data[2].(float64),
|
||||
Amount: data[3].(float64)})
|
||||
} else {
|
||||
newOrderbook = append(newOrderbook, WebsocketBook{
|
||||
ID: int64(data[0].(float64)),
|
||||
Price: data[1].(float64),
|
||||
Amount: data[2].(float64)})
|
||||
}
|
||||
err := b.WsInsertSnapshot(curr,
|
||||
asset.Spot,
|
||||
newOrderbook)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s",
|
||||
err)
|
||||
}
|
||||
case float64:
|
||||
}
|
||||
err := b.WsInsertSnapshot(pair, chanAsset, newOrderbook)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s",
|
||||
err)
|
||||
}
|
||||
case float64:
|
||||
if len(obSnapBundle) == 4 {
|
||||
newOrderbook = append(newOrderbook, WebsocketBook{
|
||||
ID: int64(id),
|
||||
Period: int64(obSnapBundle[1].(float64)),
|
||||
Rate: obSnapBundle[2].(float64),
|
||||
Amount: obSnapBundle[3].(float64)})
|
||||
} else {
|
||||
newOrderbook = append(newOrderbook, WebsocketBook{
|
||||
ID: int64(id),
|
||||
Price: obSnapBundle[1].(float64),
|
||||
Amount: obSnapBundle[2].(float64)})
|
||||
err := b.WsUpdateOrderbook(curr,
|
||||
asset.Spot,
|
||||
newOrderbook)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
err := b.WsUpdateOrderbook(pair, chanAsset, newOrderbook)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitfinex_websocket.go updating orderbook error: %s",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case wsCandles:
|
||||
curr := currency.NewPairFromString(chanInfo.Pair)
|
||||
if candleBundle, ok := d[1].([]interface{}); ok {
|
||||
if len(candleBundle) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch candleData := candleBundle[0].(type) {
|
||||
case []interface{}:
|
||||
b.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(0, int64(candleData[0].(float64))),
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
Pair: curr,
|
||||
OpenPrice: candleData[1].(float64),
|
||||
ClosePrice: candleData[2].(float64),
|
||||
HighPrice: candleData[3].(float64),
|
||||
LowPrice: candleData[4].(float64),
|
||||
Volume: candleData[5].(float64),
|
||||
for i := range candleBundle {
|
||||
element := candleBundle[i].([]interface{})
|
||||
b.Websocket.DataHandler <- stream.KlineData{
|
||||
Timestamp: time.Unix(0, int64(element[0].(float64))*int64(time.Millisecond)),
|
||||
Exchange: b.Name,
|
||||
AssetType: chanAsset,
|
||||
Pair: pair,
|
||||
OpenPrice: element[1].(float64),
|
||||
ClosePrice: element[2].(float64),
|
||||
HighPrice: element[3].(float64),
|
||||
LowPrice: element[4].(float64),
|
||||
Volume: element[5].(float64),
|
||||
}
|
||||
}
|
||||
|
||||
case float64:
|
||||
b.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(0, int64(candleData)),
|
||||
b.Websocket.DataHandler <- stream.KlineData{
|
||||
Timestamp: time.Unix(0, int64(candleData)*int64(time.Millisecond)),
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
Pair: curr,
|
||||
AssetType: chanAsset,
|
||||
Pair: pair,
|
||||
OpenPrice: candleBundle[1].(float64),
|
||||
ClosePrice: candleBundle[2].(float64),
|
||||
HighPrice: candleBundle[3].(float64),
|
||||
@@ -221,8 +291,8 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
Volume: tickerData[7].(float64),
|
||||
High: tickerData[8].(float64),
|
||||
Low: tickerData[9].(float64),
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromString(chanInfo.Pair),
|
||||
AssetType: chanAsset,
|
||||
Pair: pair,
|
||||
}
|
||||
return nil
|
||||
case wsTrades:
|
||||
@@ -233,22 +303,20 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
for i := range snapshot {
|
||||
elem := snapshot[i].([]interface{})
|
||||
if len(elem) == 5 {
|
||||
trades = append(trades,
|
||||
WebsocketTrade{
|
||||
ID: int64(elem[0].(float64)),
|
||||
Timestamp: int64(elem[1].(float64)),
|
||||
Amount: elem[2].(float64),
|
||||
Rate: elem[3].(float64),
|
||||
Period: int64(elem[4].(float64)),
|
||||
})
|
||||
trades = append(trades, WebsocketTrade{
|
||||
ID: int64(elem[0].(float64)),
|
||||
Timestamp: int64(elem[1].(float64)),
|
||||
Amount: elem[2].(float64),
|
||||
Rate: elem[3].(float64),
|
||||
Period: int64(elem[4].(float64)),
|
||||
})
|
||||
} else {
|
||||
trades = append(trades,
|
||||
WebsocketTrade{
|
||||
ID: int64(elem[0].(float64)),
|
||||
Timestamp: int64(elem[1].(float64)),
|
||||
Amount: elem[2].(float64),
|
||||
Price: elem[3].(float64),
|
||||
})
|
||||
trades = append(trades, WebsocketTrade{
|
||||
ID: int64(elem[0].(float64)),
|
||||
Timestamp: int64(elem[1].(float64)),
|
||||
Amount: elem[2].(float64),
|
||||
Price: elem[3].(float64),
|
||||
})
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
@@ -260,11 +328,22 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
return nil
|
||||
}
|
||||
data := d[2].([]interface{})
|
||||
trades = append(trades, WebsocketTrade{
|
||||
ID: int64(data[0].(float64)),
|
||||
Timestamp: int64(data[1].(float64)),
|
||||
Price: data[3].(float64),
|
||||
Amount: data[2].(float64)})
|
||||
if len(data) == 5 {
|
||||
trades = append(trades, WebsocketTrade{
|
||||
ID: int64(data[0].(float64)),
|
||||
Timestamp: int64(data[1].(float64)),
|
||||
Amount: data[2].(float64),
|
||||
Rate: data[3].(float64),
|
||||
Period: int64(data[4].(float64)),
|
||||
})
|
||||
} else {
|
||||
trades = append(trades, WebsocketTrade{
|
||||
ID: int64(data[0].(float64)),
|
||||
Timestamp: int64(data[1].(float64)),
|
||||
Amount: data[2].(float64),
|
||||
Price: data[3].(float64),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for i := range trades {
|
||||
@@ -276,29 +355,30 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
|
||||
if trades[i].Rate > 0 {
|
||||
b.Websocket.DataHandler <- wshandler.FundingData{
|
||||
CurrencyPair: currency.NewPairFromString(chanInfo.Pair),
|
||||
b.Websocket.DataHandler <- stream.FundingData{
|
||||
CurrencyPair: pair,
|
||||
Timestamp: time.Unix(0, trades[i].Timestamp*int64(time.Millisecond)),
|
||||
Amount: newAmount,
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
AssetType: chanAsset,
|
||||
Side: side,
|
||||
Rate: trades[i].Rate,
|
||||
Period: trades[i].Period,
|
||||
}
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
CurrencyPair: currency.NewPairFromString(chanInfo.Pair),
|
||||
b.Websocket.DataHandler <- stream.TradeData{
|
||||
CurrencyPair: pair,
|
||||
Timestamp: time.Unix(0, trades[i].Timestamp*int64(time.Millisecond)),
|
||||
Price: trades[i].Price,
|
||||
Amount: newAmount,
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
AssetType: chanAsset,
|
||||
Side: side,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if authResp, ok := d[1].(string); ok {
|
||||
@@ -315,8 +395,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
strings.Contains(channelName, wsFundingOrderCancelRequest):
|
||||
if data[0] != nil && data[0].(float64) > 0 {
|
||||
id := int64(data[0].(float64))
|
||||
if b.WebsocketConn.IsIDWaitingForResponse(id) {
|
||||
b.AuthenticatedWebsocketConn.SetResponseIDAndData(id, respRaw)
|
||||
if b.Websocket.Match.IncomingWithData(id, respRaw) {
|
||||
return nil
|
||||
}
|
||||
b.wsHandleFundingOffer(data)
|
||||
@@ -326,19 +405,23 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
strings.Contains(channelName, wsOrderCancelRequest):
|
||||
if data[2] != nil && data[2].(float64) > 0 {
|
||||
id := int64(data[2].(float64))
|
||||
if b.WebsocketConn.IsIDWaitingForResponse(id) {
|
||||
b.AuthenticatedWebsocketConn.SetResponseIDAndData(id, respRaw)
|
||||
if b.Websocket.Match.IncomingWithData(id, respRaw) {
|
||||
return nil
|
||||
}
|
||||
b.wsHandleOrder(data)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%s - Unexpected data returned %s", b.Name, respRaw)
|
||||
return fmt.Errorf("%s - Unexpected data returned %s",
|
||||
b.Name,
|
||||
respRaw)
|
||||
}
|
||||
}
|
||||
if notification[5] != nil && strings.EqualFold(notification[5].(string), wsError) {
|
||||
return fmt.Errorf("%s - Error %s", b.Name, notification[6].(string))
|
||||
if notification[5] != nil &&
|
||||
strings.EqualFold(notification[5].(string), wsError) {
|
||||
return fmt.Errorf("%s - Error %s",
|
||||
b.Name,
|
||||
notification[6].(string))
|
||||
}
|
||||
case wsOrderSnapshot:
|
||||
if snapBundle, ok := d[2].([]interface{}); ok && len(snapBundle) > 0 {
|
||||
@@ -634,7 +717,9 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
}
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
b.Websocket.DataHandler <- stream.UnhandledMessageWarning{
|
||||
Message: b.Name + stream.UnhandledMessage + string(respRaw),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -781,20 +866,13 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books
|
||||
newOrderBook.Pair = p
|
||||
newOrderBook.ExchangeName = b.Name
|
||||
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitfinex.go error - %s", err)
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
|
||||
Asset: assetType,
|
||||
Exchange: b.Name}
|
||||
return nil
|
||||
return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
}
|
||||
|
||||
// WsUpdateOrderbook updates the orderbook list, removing and adding to the
|
||||
// orderbook sides
|
||||
func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book []WebsocketBook) error {
|
||||
orderbookUpdate := wsorderbook.WebsocketOrderbookUpdate{
|
||||
orderbookUpdate := buffer.Update{
|
||||
Asset: assetType,
|
||||
Pair: p,
|
||||
}
|
||||
@@ -833,97 +911,115 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book
|
||||
}
|
||||
}
|
||||
}
|
||||
err := b.Websocket.Orderbook.Update(&orderbookUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
|
||||
Asset: assetType,
|
||||
Exchange: b.Name}
|
||||
|
||||
return nil
|
||||
return b.Websocket.Orderbook.Update(&orderbookUpdate)
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *Bitfinex) GenerateDefaultSubscriptions() {
|
||||
func (b *Bitfinex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var channels = []string{
|
||||
wsBook,
|
||||
wsTrades,
|
||||
wsTicker,
|
||||
wsCandles,
|
||||
}
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
for i := range channels {
|
||||
enabledPairs := b.GetEnabledPairs(asset.Spot)
|
||||
for j := range enabledPairs {
|
||||
if strings.HasPrefix(enabledPairs[j].Base.String(), "f") {
|
||||
log.Warnf(log.WebsocketMgr,
|
||||
"%v - Websocket does not support funding currency %v, skipping",
|
||||
b.Name, enabledPairs[j])
|
||||
continue
|
||||
}
|
||||
b.appendOptionalDelimiter(&enabledPairs[j])
|
||||
params := make(map[string]interface{})
|
||||
if channels[i] == wsBook {
|
||||
params["prec"] = "R0"
|
||||
params["len"] = "100"
|
||||
}
|
||||
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: channels[i],
|
||||
Currency: enabledPairs[j],
|
||||
Params: params,
|
||||
})
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
assets := b.GetAssetTypes()
|
||||
for i := range assets {
|
||||
enabledPairs, err := b.GetEnabledPairs(assets[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for j := range channels {
|
||||
for k := range enabledPairs {
|
||||
params := make(map[string]interface{})
|
||||
if channels[j] == wsBook {
|
||||
params["prec"] = "R0"
|
||||
params["len"] = "100"
|
||||
}
|
||||
|
||||
if channels[j] == wsCandles {
|
||||
// TODO: Add ability to select timescale && funding period
|
||||
var fundingPeriod string
|
||||
prefix := "t"
|
||||
if assets[i] == asset.MarginFunding {
|
||||
prefix = "f"
|
||||
fundingPeriod = ":p30"
|
||||
}
|
||||
params["key"] = "trade:1m:" + prefix + enabledPairs[k].String() + fundingPeriod
|
||||
} else {
|
||||
params["symbol"] = enabledPairs[k].String()
|
||||
}
|
||||
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[j],
|
||||
Currency: enabledPairs[k],
|
||||
Params: params,
|
||||
Asset: assets[i],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
b.Websocket.SubscribeToChannels(subscriptions)
|
||||
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *Bitfinex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
req := make(map[string]interface{})
|
||||
req["event"] = "subscribe"
|
||||
req["channel"] = channelToSubscribe.Channel
|
||||
func (b *Bitfinex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
var errs common.Errors
|
||||
for i := range channelsToSubscribe {
|
||||
req := make(map[string]interface{})
|
||||
req["event"] = "subscribe"
|
||||
req["channel"] = channelsToSubscribe[i].Channel
|
||||
|
||||
if channelToSubscribe.Currency.String() != "" {
|
||||
if channelToSubscribe.Channel == wsCandles {
|
||||
// TODO: Add ability to select timescale
|
||||
req["key"] = fmt.Sprintf("trade:1D:%v",
|
||||
b.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String())
|
||||
} else {
|
||||
req["symbol"] = b.FormatExchangeCurrency(channelToSubscribe.Currency,
|
||||
asset.Spot).String()
|
||||
}
|
||||
}
|
||||
|
||||
if len(channelToSubscribe.Params) > 0 {
|
||||
for k, v := range channelToSubscribe.Params {
|
||||
for k, v := range channelsToSubscribe[i].Params {
|
||||
req[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return b.WebsocketConn.SendJSONMessage(req)
|
||||
err := b.Websocket.Conn.SendJSONMessage(req)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
|
||||
}
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (b *Bitfinex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
req := make(map[string]interface{})
|
||||
req["event"] = "unsubscribe"
|
||||
req["channel"] = channelToSubscribe.Channel
|
||||
func (b *Bitfinex) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
var errs common.Errors
|
||||
for i := range channelsToUnsubscribe {
|
||||
req := make(map[string]interface{})
|
||||
req["event"] = "unsubscribe"
|
||||
req["channel"] = channelsToUnsubscribe[i].Channel
|
||||
|
||||
if len(channelToSubscribe.Params) > 0 {
|
||||
for k, v := range channelToSubscribe.Params {
|
||||
for k, v := range channelsToUnsubscribe[i].Params {
|
||||
req[k] = v
|
||||
}
|
||||
|
||||
err := b.Websocket.Conn.SendJSONMessage(req)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
|
||||
}
|
||||
return b.WebsocketConn.SendJSONMessage(req)
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsSendAuth sends a autheticated event payload
|
||||
func (b *Bitfinex) WsSendAuth() error {
|
||||
if !b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", b.Name)
|
||||
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled",
|
||||
b.Name)
|
||||
}
|
||||
nonce := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
payload := "AUTH" + nonce
|
||||
@@ -931,15 +1027,13 @@ func (b *Bitfinex) WsSendAuth() error {
|
||||
Event: "auth",
|
||||
APIKey: b.API.Credentials.Key,
|
||||
AuthPayload: payload,
|
||||
AuthSig: crypto.HexEncodeToString(
|
||||
crypto.GetHMAC(
|
||||
crypto.HashSHA512_384,
|
||||
[]byte(payload),
|
||||
[]byte(b.API.Credentials.Secret))),
|
||||
AuthSig: crypto.HexEncodeToString(crypto.GetHMAC(crypto.HashSHA512_384,
|
||||
[]byte(payload),
|
||||
[]byte(b.API.Credentials.Secret))),
|
||||
AuthNonce: nonce,
|
||||
DeadManSwitch: 0,
|
||||
}
|
||||
err := b.AuthenticatedWebsocketConn.SendJSONMessage(request)
|
||||
err := b.Websocket.AuthConn.SendJSONMessage(request)
|
||||
if err != nil {
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
return err
|
||||
@@ -954,7 +1048,8 @@ func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) {
|
||||
b.WebsocketSubdChannels[chanID] = chanInfo
|
||||
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n",
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n",
|
||||
b.Name,
|
||||
channel,
|
||||
pair,
|
||||
@@ -964,9 +1059,9 @@ func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) {
|
||||
|
||||
// WsNewOrder authenticated new order request
|
||||
func (b *Bitfinex) WsNewOrder(data *WsNewOrderRequest) (string, error) {
|
||||
data.CustomID = b.AuthenticatedWebsocketConn.GenerateMessageID(false)
|
||||
data.CustomID = b.Websocket.AuthConn.GenerateMessageID(false)
|
||||
request := makeRequestInterface(wsOrderNew, data)
|
||||
resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(data.CustomID, request)
|
||||
resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(data.CustomID, request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -997,7 +1092,7 @@ func (b *Bitfinex) WsNewOrder(data *WsNewOrderRequest) (string, error) {
|
||||
// WsModifyOrder authenticated modify order request
|
||||
func (b *Bitfinex) WsModifyOrder(data *WsUpdateOrderRequest) error {
|
||||
request := makeRequestInterface(wsOrderUpdate, data)
|
||||
resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(data.OrderID, request)
|
||||
resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(data.OrderID, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1026,7 +1121,7 @@ func (b *Bitfinex) WsCancelMultiOrders(orderIDs []int64) error {
|
||||
OrderID: orderIDs,
|
||||
}
|
||||
request := makeRequestInterface(wsCancelMultipleOrders, cancel)
|
||||
return b.AuthenticatedWebsocketConn.SendJSONMessage(request)
|
||||
return b.Websocket.AuthConn.SendJSONMessage(request)
|
||||
}
|
||||
|
||||
// WsCancelOrder authenticated cancel order request
|
||||
@@ -1035,7 +1130,7 @@ func (b *Bitfinex) WsCancelOrder(orderID int64) error {
|
||||
OrderID: orderID,
|
||||
}
|
||||
request := makeRequestInterface(wsOrderCancel, cancel)
|
||||
resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(orderID, request)
|
||||
resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(orderID, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1061,13 +1156,13 @@ func (b *Bitfinex) WsCancelOrder(orderID int64) error {
|
||||
func (b *Bitfinex) WsCancelAllOrders() error {
|
||||
cancelAll := WsCancelAllOrdersRequest{All: 1}
|
||||
request := makeRequestInterface(wsCancelMultipleOrders, cancelAll)
|
||||
return b.AuthenticatedWebsocketConn.SendJSONMessage(request)
|
||||
return b.Websocket.AuthConn.SendJSONMessage(request)
|
||||
}
|
||||
|
||||
// WsNewOffer authenticated new offer request
|
||||
func (b *Bitfinex) WsNewOffer(data *WsNewOfferRequest) error {
|
||||
request := makeRequestInterface(wsFundingOrderNew, data)
|
||||
return b.AuthenticatedWebsocketConn.SendJSONMessage(request)
|
||||
return b.Websocket.AuthConn.SendJSONMessage(request)
|
||||
}
|
||||
|
||||
// WsCancelOffer authenticated cancel offer request
|
||||
@@ -1076,7 +1171,7 @@ func (b *Bitfinex) WsCancelOffer(orderID int64) error {
|
||||
OrderID: orderID,
|
||||
}
|
||||
request := makeRequestInterface(wsFundingOrderCancel, cancel)
|
||||
resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(orderID, request)
|
||||
resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(orderID, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"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/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -57,17 +57,27 @@ func (b *Bitfinex) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresKey = true
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
fmt1 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
}
|
||||
|
||||
fmt2 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: ":"},
|
||||
}
|
||||
|
||||
err := b.StoreAssetPairFormat(asset.Spot, fmt1)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
err = b.StoreAssetPairFormat(asset.Margin, fmt2)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
err = b.StoreAssetPairFormat(asset.MarginFunding, fmt1)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
b.Features = exchange.Features{
|
||||
@@ -111,7 +121,6 @@ func (b *Bitfinex) SetDefaults() {
|
||||
OrderbookFetching: true,
|
||||
AccountInfo: true,
|
||||
Subscribe: true,
|
||||
Unsubscribe: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
MessageCorrelation: true,
|
||||
DeadMansSwitch: true,
|
||||
@@ -155,7 +164,7 @@ func (b *Bitfinex) SetDefaults() {
|
||||
b.API.Endpoints.URLDefault = bitfinexAPIURLBase
|
||||
b.API.Endpoints.URL = b.API.Endpoints.URLDefault
|
||||
b.API.Endpoints.WebsocketURL = publicBitfinexWebsocketEndpoint
|
||||
b.Websocket = wshandler.New()
|
||||
b.Websocket = stream.New()
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
@@ -173,49 +182,41 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Websocket.Setup(
|
||||
&wshandler.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: publicBitfinexWebsocketEndpoint,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
err = b.Websocket.Setup(&stream.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: publicBitfinexWebsocketEndpoint,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
GenerateSubscriptions: b.GenerateDefaultSubscriptions,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit,
|
||||
UpdateEntriesByID: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: b.Websocket.GetWebsocketURL(),
|
||||
ProxyURL: b.Websocket.GetProxyAddress(),
|
||||
Verbose: b.Verbose,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
b.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: authenticatedBitfinexWebsocketEndpoint,
|
||||
ProxyURL: b.Websocket.GetProxyAddress(),
|
||||
Verbose: b.Verbose,
|
||||
err = b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
URL: publicBitfinexWebsocketEndpoint,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
exch.Name)
|
||||
return nil
|
||||
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
URL: authenticatedBitfinexWebsocketEndpoint,
|
||||
Authenticated: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Start starts the Bitfinex go routine
|
||||
@@ -244,7 +245,9 @@ func (b *Bitfinex) Run() {
|
||||
err := b.UpdateTradablePairs(false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update tradable pairs. Err: %s", b.Name, err)
|
||||
"%s failed to update tradable pairs. Err: %s",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +268,13 @@ func (b *Bitfinex) FetchTradablePairs(a asset.Item) ([]string, error) {
|
||||
symbols = append(symbols, k[1:])
|
||||
}
|
||||
case asset.Margin:
|
||||
for k := range items {
|
||||
if !strings.Contains(k, ":") {
|
||||
continue
|
||||
}
|
||||
symbols = append(symbols, k[1:])
|
||||
}
|
||||
case asset.MarginFunding:
|
||||
for k := range items {
|
||||
if !strings.HasPrefix(k, "f") {
|
||||
continue
|
||||
@@ -281,15 +291,19 @@ func (b *Bitfinex) FetchTradablePairs(a asset.Item) ([]string, error) {
|
||||
// UpdateTradablePairs updates the exchanges available pairs and stores
|
||||
// them in the exchanges config
|
||||
func (b *Bitfinex) UpdateTradablePairs(forceUpdate bool) error {
|
||||
for i := range b.CurrencyPairs.AssetTypes {
|
||||
pairs, err := b.FetchTradablePairs(b.CurrencyPairs.AssetTypes[i])
|
||||
assets := b.CurrencyPairs.GetAssetTypes()
|
||||
for i := range assets {
|
||||
pairs, err := b.FetchTradablePairs(assets[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = b.UpdatePairs(currency.NewPairsFromStrings(pairs),
|
||||
b.CurrencyPairs.AssetTypes[i],
|
||||
false,
|
||||
forceUpdate)
|
||||
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(p, assets[i], false, forceUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -299,34 +313,40 @@ func (b *Bitfinex) UpdateTradablePairs(forceUpdate bool) error {
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
enabledPairs := b.GetEnabledPairs(assetType)
|
||||
enabledPairs, err := b.GetEnabledPairs(assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tickerNew, err := b.GetTickerBatch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range tickerNew {
|
||||
if strings.HasPrefix(k, "f") {
|
||||
continue
|
||||
}
|
||||
pair := currency.NewPairFromString(k[1:]) // Remove prefix
|
||||
if !enabledPairs.Contains(p, true) {
|
||||
continue
|
||||
}
|
||||
tick := ticker.Price{
|
||||
Last: v.Last,
|
||||
High: v.High,
|
||||
Low: v.Low,
|
||||
Bid: v.Bid,
|
||||
Ask: v.Ask,
|
||||
Volume: v.Volume,
|
||||
Pair: pair,
|
||||
}
|
||||
err = ticker.ProcessTicker(b.Name, &tick, assetType)
|
||||
pair, err := currency.NewPairFromString(k[1:]) // Remove prefix
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !enabledPairs.Contains(pair, true) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Last: v.Last,
|
||||
High: v.High,
|
||||
Low: v.Low,
|
||||
Bid: v.Bid,
|
||||
Ask: v.Ask,
|
||||
Volume: v.Volume,
|
||||
Pair: pair,
|
||||
AssetType: assetType,
|
||||
ExchangeName: b.Name})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ticker.GetTicker(b.Name, p, assetType)
|
||||
}
|
||||
|
||||
@@ -354,7 +374,7 @@ func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType asset.Item) (*order
|
||||
func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
b.appendOptionalDelimiter(&p)
|
||||
var prefix = "t"
|
||||
if assetType == asset.Margin {
|
||||
if assetType == asset.MarginFunding {
|
||||
prefix = "f"
|
||||
}
|
||||
|
||||
@@ -459,11 +479,17 @@ func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
|
||||
fpair, err := b.FormatExchangeCurrency(o.Pair, o.AssetType)
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
|
||||
if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
|
||||
submitOrderResponse.OrderID, err = b.WsNewOrder(&WsNewOrderRequest{
|
||||
CustomID: b.AuthenticatedWebsocketConn.GenerateMessageID(false),
|
||||
CustomID: b.Websocket.AuthConn.GenerateMessageID(false),
|
||||
Type: o.Type.String(),
|
||||
Symbol: b.FormatExchangeCurrency(o.Pair, asset.Spot).String(),
|
||||
Symbol: fpair.String(),
|
||||
Amount: o.Amount,
|
||||
Price: o.Price,
|
||||
})
|
||||
@@ -473,18 +499,22 @@ func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) {
|
||||
} else {
|
||||
var response Order
|
||||
isBuying := o.Side == order.Buy
|
||||
b.appendOptionalDelimiter(&o.Pair)
|
||||
response, err = b.NewOrder(o.Pair.String(),
|
||||
o.Type.String(),
|
||||
b.appendOptionalDelimiter(&fpair)
|
||||
orderType := o.Type.Lower()
|
||||
if o.AssetType == asset.Spot {
|
||||
orderType = "exchange " + orderType
|
||||
}
|
||||
response, err = b.NewOrder(fpair.String(),
|
||||
orderType,
|
||||
o.Amount,
|
||||
o.Price,
|
||||
false,
|
||||
isBuying)
|
||||
isBuying,
|
||||
false)
|
||||
if err != nil {
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
if response.OrderID > 0 {
|
||||
submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10)
|
||||
if response.ID > 0 {
|
||||
submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10)
|
||||
}
|
||||
if response.RemainingAmount == 0 {
|
||||
submitOrderResponse.FullyMatched = true
|
||||
@@ -615,11 +645,6 @@ func (b *Bitfinex) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdra
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *Bitfinex) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return b.Websocket, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (b *Bitfinex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if !b.AllowAuthenticatedRequest() && // Todo check connection status
|
||||
@@ -639,23 +664,27 @@ func (b *Bitfinex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
|
||||
for i := range resp {
|
||||
orderSide := order.Side(strings.ToUpper(resp[i].Side))
|
||||
timestamp, err := strconv.ParseInt(resp[i].Timestamp, 10, 64)
|
||||
timestamp, err := strconv.ParseFloat(resp[i].Timestamp, 64)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"Unable to convert timestamp '%s', leaving blank",
|
||||
resp[i].Timestamp)
|
||||
}
|
||||
orderDate := time.Unix(timestamp, 0)
|
||||
|
||||
pair, err := currency.NewPairFromString(resp[i].Symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderDetail := order.Detail{
|
||||
Amount: resp[i].OriginalAmount,
|
||||
Date: orderDate,
|
||||
Date: time.Unix(int64(timestamp), 0),
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Side: orderSide,
|
||||
Price: resp[i].Price,
|
||||
RemainingAmount: resp[i].RemainingAmount,
|
||||
Pair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Pair: pair,
|
||||
ExecutedAmount: resp[i].ExecutedAmount,
|
||||
}
|
||||
|
||||
@@ -706,16 +735,21 @@ func (b *Bitfinex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
}
|
||||
orderDate := time.Unix(timestamp, 0)
|
||||
|
||||
pair, err := currency.NewPairFromString(resp[i].Symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderDetail := order.Detail{
|
||||
Amount: resp[i].OriginalAmount,
|
||||
Date: orderDate,
|
||||
Exchange: b.Name,
|
||||
ID: strconv.FormatInt(resp[i].OrderID, 10),
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
Side: orderSide,
|
||||
Price: resp[i].Price,
|
||||
RemainingAmount: resp[i].RemainingAmount,
|
||||
ExecutedAmount: resp[i].ExecutedAmount,
|
||||
Pair: currency.NewPairFromString(resp[i].Symbol),
|
||||
Pair: pair,
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -751,31 +785,6 @@ func (b *Bitfinex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *Bitfinex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
for i := range channels {
|
||||
b.appendOptionalDelimiter(&channels[i].Currency)
|
||||
}
|
||||
b.Websocket.SubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *Bitfinex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
for i := range channels {
|
||||
b.appendOptionalDelimiter(&channels[i].Currency)
|
||||
}
|
||||
b.Websocket.RemoveSubscribedChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *Bitfinex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return b.Websocket.GetSubscriptions(), nil
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *Bitfinex) AuthenticateWebsocket() error {
|
||||
return b.WsSendAuth()
|
||||
@@ -822,7 +831,12 @@ func (b *Bitfinex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, e
|
||||
return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits)
|
||||
}
|
||||
|
||||
candles, err := b.GetCandles(b.fixCasing(pair, a), b.FormatExchangeKlineInterval(interval),
|
||||
cf, err := b.fixCasing(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
candles, err := b.GetCandles(cf, b.FormatExchangeKlineInterval(interval),
|
||||
start.Unix()*1000, end.Unix()*1000,
|
||||
b.Features.Enabled.Kline.ResultLimit, true)
|
||||
if err != nil {
|
||||
@@ -866,8 +880,13 @@ func (b *Bitfinex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
cf, err := b.fixCasing(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for x := range dates {
|
||||
candles, err := b.GetCandles(b.fixCasing(pair, a), b.FormatExchangeKlineInterval(interval),
|
||||
candles, err := b.GetCandles(cf, b.FormatExchangeKlineInterval(interval),
|
||||
dates[x].Start.Unix()*1000, dates[x].End.Unix()*1000,
|
||||
b.Features.Enabled.Kline.ResultLimit, true)
|
||||
if err != nil {
|
||||
@@ -890,7 +909,7 @@ func (b *Bitfinex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item,
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) string {
|
||||
func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) (string, error) {
|
||||
var checkString [2]byte
|
||||
if a == asset.Spot {
|
||||
checkString[0] = 't'
|
||||
@@ -900,13 +919,18 @@ func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) string {
|
||||
checkString[1] = 'F'
|
||||
}
|
||||
|
||||
fmt, err := b.FormatExchangeCurrency(in, a)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
y := in.Base.String()
|
||||
if (y[0] != checkString[0] && y[0] != checkString[1]) ||
|
||||
(y[0] == checkString[1] && y[1] == checkString[1]) || in.Base == currency.TNB {
|
||||
return string(checkString[0]) + b.FormatExchangeCurrency(in, a).Upper().String()
|
||||
return string(checkString[0]) + fmt.Upper().String(), nil
|
||||
}
|
||||
|
||||
runes := []rune(b.FormatExchangeCurrency(in, a).Upper().String())
|
||||
runes := []rune(fmt.Upper().String())
|
||||
runes[0] = unicode.ToLower(runes[0])
|
||||
return string(runes)
|
||||
return string(runes), nil
|
||||
}
|
||||
|
||||
@@ -133,7 +133,10 @@ func TestGetExchangeStatus(t *testing.T) {
|
||||
|
||||
func TestCheckFXString(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := currency.NewPairDelimiter("FXBTC_JPY", "_")
|
||||
p, err := currency.NewPairDelimiter("FXBTC_JPY", "_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p = b.CheckFXString(p)
|
||||
if p.Base.String() != "FX_BTC" {
|
||||
t.Error("Bitflyer - CheckFXString() error")
|
||||
@@ -144,15 +147,19 @@ func TestFetchTicker(t *testing.T) {
|
||||
t.Parallel()
|
||||
var p currency.Pair
|
||||
|
||||
currencies := b.GetAvailablePairs(asset.Spot)
|
||||
for _, pair := range currencies {
|
||||
if pair.String() == "FXBTC_JPY" {
|
||||
p = pair
|
||||
currencies, err := b.GetAvailablePairs(asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range currencies {
|
||||
if currencies[i].String() == "FXBTC_JPY" {
|
||||
p = currencies[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_, err := b.FetchTicker(p, asset.Spot)
|
||||
_, err = b.FetchTicker(p, asset.Spot)
|
||||
if err != nil {
|
||||
t.Error("Bitflyer - FetchTicker() error", err)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -53,20 +52,20 @@ func (b *Bitflyer) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresKey = true
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
asset.Futures,
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Delimiter: "_",
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Delimiter: "_",
|
||||
Uppercase: true,
|
||||
},
|
||||
requestFmt := ¤cy.PairFormat{
|
||||
Delimiter: currency.UnderscoreDelimiter,
|
||||
Uppercase: true,
|
||||
}
|
||||
configFmt := ¤cy.PairFormat{
|
||||
Delimiter: currency.UnderscoreDelimiter,
|
||||
Uppercase: true,
|
||||
}
|
||||
err := b.SetGlobalPairsManager(requestFmt,
|
||||
configFmt,
|
||||
asset.Spot,
|
||||
asset.Futures)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
b.Features = exchange.Features{
|
||||
@@ -105,7 +104,6 @@ func (b *Bitflyer) Setup(exch *config.ExchangeConfig) error {
|
||||
b.SetEnabled(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
return b.SetupDefaults(exch)
|
||||
}
|
||||
|
||||
@@ -141,6 +139,11 @@ func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(assetType, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var products []string
|
||||
for i := range pairs {
|
||||
if pairs[i].Alias != "" && assetType == asset.Futures {
|
||||
@@ -148,7 +151,7 @@ func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) {
|
||||
} else if pairs[i].Alias == "" &&
|
||||
assetType == asset.Spot &&
|
||||
strings.Contains(pairs[i].ProductCode,
|
||||
b.GetPairFormat(assetType, false).Delimiter) {
|
||||
format.Delimiter) {
|
||||
products = append(products, pairs[i].ProductCode)
|
||||
}
|
||||
}
|
||||
@@ -158,17 +161,19 @@ func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) {
|
||||
// UpdateTradablePairs updates the exchanges available pairs and stores
|
||||
// them in the exchanges config
|
||||
func (b *Bitflyer) UpdateTradablePairs(forceUpdate bool) error {
|
||||
for x := range b.CurrencyPairs.AssetTypes {
|
||||
a := b.CurrencyPairs.AssetTypes[x]
|
||||
pairs, err := b.FetchTradablePairs(a)
|
||||
assets := b.CurrencyPairs.GetAssetTypes()
|
||||
for x := range assets {
|
||||
pairs, err := b.FetchTradablePairs(assets[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(currency.NewPairsFromStrings(pairs),
|
||||
a,
|
||||
false,
|
||||
forceUpdate)
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(p, assets[x], false, forceUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,23 +183,21 @@ func (b *Bitflyer) UpdateTradablePairs(forceUpdate bool) error {
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerPrice := new(ticker.Price)
|
||||
|
||||
p = b.CheckFXString(p)
|
||||
|
||||
tickerNew, err := b.GetTicker(p.String())
|
||||
tickerNew, err := b.GetTicker(b.CheckFXString(p).String())
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tickerPrice.Pair = p
|
||||
tickerPrice.Ask = tickerNew.BestAsk
|
||||
tickerPrice.Bid = tickerNew.BestBid
|
||||
tickerPrice.Last = tickerNew.Last
|
||||
tickerPrice.Volume = tickerNew.Volume
|
||||
err = ticker.ProcessTicker(b.Name, tickerPrice, assetType)
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Pair: p,
|
||||
Ask: tickerNew.BestAsk,
|
||||
Bid: tickerNew.BestBid,
|
||||
Last: tickerNew.Last,
|
||||
Volume: tickerNew.Volume,
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType})
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ticker.GetTicker(b.Name, p, assetType)
|
||||
@@ -231,9 +234,7 @@ func (b *Bitflyer) FetchOrderbook(p currency.Pair, assetType asset.Item) (*order
|
||||
func (b *Bitflyer) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
orderBook := new(orderbook.Base)
|
||||
|
||||
p = b.CheckFXString(p)
|
||||
|
||||
orderbookNew, err := b.GetOrderBook(p.String())
|
||||
orderbookNew, err := b.GetOrderBook(b.CheckFXString(p).String())
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
}
|
||||
@@ -337,11 +338,6 @@ func (b *Bitflyer) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdra
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *Bitflyer) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *Bitflyer) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
@@ -362,28 +358,6 @@ func (b *Bitflyer) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error
|
||||
return b.GetFee(feeBuilder)
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *Bitflyer) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *Bitflyer) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *Bitflyer) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *Bitflyer) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (b *Bitflyer) ValidateCredentials() error {
|
||||
|
||||
@@ -207,6 +207,20 @@ func TestMarketSellOrder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTicker(t *testing.T) {
|
||||
cp := currency.NewPair(currency.QTUM, currency.KRW)
|
||||
_, err := b.UpdateTicker(cp, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cp = currency.NewPair(currency.DASH, currency.KRW)
|
||||
_, err = b.UpdateTicker(cp, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setFeeBuilder() *exchange.FeeBuilder {
|
||||
return &exchange.FeeBuilder{
|
||||
Amount: 1,
|
||||
@@ -436,8 +450,11 @@ func TestGetAccountInfo(t *testing.T) {
|
||||
|
||||
func TestModifyOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
_, err := b.ModifyOrder(&order.Modify{
|
||||
curr, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.ModifyOrder(&order.Modify{
|
||||
ID: "1337",
|
||||
Price: 100,
|
||||
Amount: 1000,
|
||||
@@ -535,18 +552,24 @@ func TestGetCandleStick(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTC_KRW")
|
||||
currencyPair, err := currency.NewPairFromString("BTC_KRW")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay)
|
||||
_, err = b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("BTC_KRW")
|
||||
currencyPair, err := currency.NewPairFromString("BTC_KRW")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startTime := time.Now().Add(-time.Hour * 24)
|
||||
_, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay)
|
||||
_, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -56,20 +55,11 @@ func (b *Bithumb) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresKey = true
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "_",
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Index: "KRW",
|
||||
},
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true, Index: "KRW"}
|
||||
err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
b.Features = exchange.Features{
|
||||
@@ -135,7 +125,6 @@ func (b *Bithumb) Setup(exch *config.ExchangeConfig) error {
|
||||
b.SetEnabled(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
return b.SetupDefaults(exch)
|
||||
}
|
||||
|
||||
@@ -186,34 +175,46 @@ func (b *Bithumb) UpdateTradablePairs(forceUpdate bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate)
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(p, asset.Spot, false, forceUpdate)
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *Bithumb) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerPrice := new(ticker.Price)
|
||||
tickers, err := b.GetAllTickers()
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
pairs := b.GetEnabledPairs(assetType)
|
||||
pairs, err := b.GetEnabledPairs(assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range pairs {
|
||||
curr := pairs[i].Base.String()
|
||||
t, ok := tickers[curr]
|
||||
if !ok {
|
||||
continue
|
||||
return nil,
|
||||
fmt.Errorf("enabled pair %s [%s] not found in returned ticker map %v",
|
||||
pairs[i], pairs, tickers)
|
||||
}
|
||||
tp := ticker.Price{
|
||||
High: t.MaxPrice,
|
||||
Low: t.MinPrice,
|
||||
Volume: t.UnitsTraded24Hr,
|
||||
Open: t.OpeningPrice,
|
||||
Close: t.ClosingPrice,
|
||||
Pair: pairs[i],
|
||||
}
|
||||
err = ticker.ProcessTicker(b.Name, &tp, assetType)
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
High: t.MaxPrice,
|
||||
Low: t.MinPrice,
|
||||
Volume: t.UnitsTraded24Hr,
|
||||
Open: t.OpeningPrice,
|
||||
Close: t.ClosingPrice,
|
||||
Pair: pairs[i],
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ticker.GetTicker(b.Name, p, assetType)
|
||||
@@ -398,7 +399,11 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *order.Cancel) (order.Cancel
|
||||
}
|
||||
|
||||
var allOrders []OrderData
|
||||
currs := b.GetEnabledPairs(asset.Spot)
|
||||
currs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return cancelAllOrdersResponse, err
|
||||
}
|
||||
|
||||
for i := range currs {
|
||||
orders, err := b.GetOrders("",
|
||||
orderCancellation.Side.String(),
|
||||
@@ -484,11 +489,6 @@ func (b *Bithumb) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *Bithumb) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (b *Bithumb) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if !b.AllowAuthenticatedRequest() && // Todo check connection status
|
||||
@@ -506,6 +506,11 @@ func (b *Bithumb) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range resp.Data {
|
||||
if resp.Data[i].Status != "placed" {
|
||||
continue
|
||||
@@ -522,7 +527,7 @@ func (b *Bithumb) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
Status: order.Active,
|
||||
Pair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency,
|
||||
resp.Data[i].PaymentCurrency,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
format.Delimiter),
|
||||
}
|
||||
|
||||
if resp.Data[i].Type == "bid" {
|
||||
@@ -549,6 +554,11 @@ func (b *Bithumb) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range resp.Data {
|
||||
if resp.Data[i].Status == "placed" {
|
||||
continue
|
||||
@@ -564,7 +574,7 @@ func (b *Bithumb) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
RemainingAmount: resp.Data[i].UnitsRemaining,
|
||||
Pair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency,
|
||||
resp.Data[i].PaymentCurrency,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
format.Delimiter),
|
||||
}
|
||||
|
||||
if resp.Data[i].Type == "bid" {
|
||||
@@ -582,28 +592,6 @@ func (b *Bithumb) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *Bithumb) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *Bithumb) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *Bithumb) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *Bithumb) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (b *Bithumb) ValidateCredentials() error {
|
||||
@@ -624,7 +612,13 @@ func (b *Bithumb) GetHistoricCandles(pair currency.Pair, a asset.Item, start, en
|
||||
}
|
||||
}
|
||||
|
||||
candle, err := b.GetCandleStick(b.FormatExchangeCurrency(pair, a).String(), b.FormatExchangeKlineInterval(interval))
|
||||
formattedPair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
candle, err := b.GetCandleStick(formattedPair.String(),
|
||||
b.FormatExchangeKlineInterval(interval))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
@@ -14,13 +14,11 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// Bitmex is the overarching type across this package
|
||||
type Bitmex struct {
|
||||
exchange.Base
|
||||
WebsocketConn *wshandler.WebsocketConnection
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
@@ -45,13 +45,11 @@ func TestMain(m *testing.M) {
|
||||
bitmexConfig.API.AuthenticatedWebsocketSupport = true
|
||||
bitmexConfig.API.Credentials.Key = apiKey
|
||||
bitmexConfig.API.Credentials.Secret = apiSecret
|
||||
|
||||
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = b.Setup(bitmexConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Bitmex setup error", err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -678,17 +676,10 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
// TestWsAuth dials websocket, sends login request.
|
||||
func TestWsAuth(t *testing.T) {
|
||||
if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() {
|
||||
t.Skip(wshandler.WebsocketNotEnabled)
|
||||
}
|
||||
b.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: b.Websocket.GetWebsocketURL(),
|
||||
Verbose: b.Verbose,
|
||||
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
|
||||
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
|
||||
t.Skip(stream.WebsocketNotEnabled)
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := b.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
err := b.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -710,6 +701,13 @@ func TestWsAuth(t *testing.T) {
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
func TestUpdateTradablePairs(t *testing.T) {
|
||||
err := b.UpdateTradablePairs(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWsPositionUpdate(t *testing.T) {
|
||||
pressXToJSON := []byte(`{"table":"position",
|
||||
"action":"update",
|
||||
@@ -800,7 +798,6 @@ func TestWSPositionUpdateHandling(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWSOrderbookHandling(t *testing.T) {
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
pressXToJSON := []byte(`{
|
||||
"table":"orderBookL2_25",
|
||||
"keys":["symbol","id","side"],
|
||||
@@ -871,7 +868,6 @@ func TestWSOrderbookHandling(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWSDeleveragePositionUpdateHandling(t *testing.T) {
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
pressXToJSON := []byte(`{"table":"position",
|
||||
"action":"update",
|
||||
"data":[{
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
@@ -67,67 +67,72 @@ const (
|
||||
// WsConnect initiates a new websocket connection
|
||||
func (b *Bitmex) WsConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
return errors.New(stream.WebsocketNotEnabled)
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := b.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
err := b.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := b.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
b.Websocket.ReadMessageErrors <- err
|
||||
return err
|
||||
resp := b.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return errors.New("connection closed")
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
var welcomeResp WebsocketWelcome
|
||||
err = json.Unmarshal(p.Raw, &welcomeResp)
|
||||
err = json.Unmarshal(resp.Raw, &welcomeResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "Successfully connected to Bitmex %s at time: %s Limit: %d",
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"Successfully connected to Bitmex %s at time: %s Limit: %d",
|
||||
welcomeResp.Info,
|
||||
welcomeResp.Timestamp,
|
||||
welcomeResp.Limit.Remaining)
|
||||
}
|
||||
|
||||
go b.wsReadData()
|
||||
b.GenerateDefaultSubscriptions()
|
||||
subs, err := b.GenerateDefaultSubscriptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Websocket.SubscribeToChannels(subs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.websocketSendAuth()
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%v - authentication failed: %v\n",
|
||||
b.Name,
|
||||
err)
|
||||
} else {
|
||||
authsubs, err := b.GenerateAuthenticatedSubscriptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Websocket.SubscribeToChannels(authsubs)
|
||||
}
|
||||
b.GenerateAuthenticatedSubscriptions()
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *Bitmex) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
defer b.Websocket.Wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
resp := b.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
|
||||
default:
|
||||
resp, err := b.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
err := b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +193,12 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
if len(orderbooks.Data) == 0 {
|
||||
return fmt.Errorf("%s - Empty orderbook data received: %s", b.Name, respRaw)
|
||||
}
|
||||
p := currency.NewPairFromString(orderbooks.Data[0].Symbol)
|
||||
var p currency.Pair
|
||||
p, err = currency.NewPairFromString(orderbooks.Data[0].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
@@ -210,13 +220,14 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if trades.Action == bitmexActionInitialData {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range trades.Data {
|
||||
var p currency.Pair
|
||||
p, err = currency.NewPairFromString(trades.Data[i].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var a asset.Item
|
||||
p := currency.NewPairFromString(trades.Data[i].Symbol)
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -230,7 +241,7 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
b.Websocket.DataHandler <- stream.TradeData{
|
||||
Timestamp: trades.Data[i].Timestamp,
|
||||
Price: trades.Data[i].Price,
|
||||
Amount: float64(trades.Data[i].Size),
|
||||
@@ -271,7 +282,12 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
|
||||
for i := range response.Data {
|
||||
p := currency.NewPairFromString(response.Data[i].Symbol)
|
||||
var p currency.Pair
|
||||
p, err = currency.NewPairFromString(response.Data[i].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
@@ -459,7 +475,7 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
b.Websocket.DataHandler <- response
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -467,7 +483,7 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
|
||||
// ProcessOrderbook processes orderbook updates
|
||||
func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType asset.Item) error {
|
||||
func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency.Pair, a asset.Item) error {
|
||||
if len(data) < 1 {
|
||||
return errors.New("bitmex_websocket.go error - no orderbook data")
|
||||
}
|
||||
@@ -490,8 +506,8 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
|
||||
ID: data[i].ID,
|
||||
})
|
||||
}
|
||||
newOrderBook.AssetType = assetType
|
||||
newOrderBook.Pair = currencyPair
|
||||
newOrderBook.AssetType = a
|
||||
newOrderBook.Pair = p
|
||||
newOrderBook.ExchangeName = b.Name
|
||||
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
@@ -499,11 +515,6 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
|
||||
return fmt.Errorf("bitmex_websocket.go process orderbook error - %s",
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: assetType,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
default:
|
||||
var asks, bids []orderbook.Item
|
||||
for i := range data {
|
||||
@@ -520,40 +531,42 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
|
||||
})
|
||||
}
|
||||
|
||||
err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
err := b.Websocket.Orderbook.Update(&buffer.Update{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: currencyPair,
|
||||
Asset: assetType,
|
||||
Pair: p,
|
||||
Asset: a,
|
||||
Action: action,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: assetType,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *Bitmex) GenerateDefaultSubscriptions() {
|
||||
func (b *Bitmex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
assets := b.GetAssetTypes()
|
||||
var allPairs currency.Pairs
|
||||
|
||||
var associatedAssets []asset.Item
|
||||
for x := range assets {
|
||||
contracts := b.GetEnabledPairs(assets[x])
|
||||
contracts, err := b.GetEnabledPairs(assets[x])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for y := range contracts {
|
||||
allPairs = allPairs.Add(contracts[y])
|
||||
associatedAssets = append(associatedAssets, assets[x])
|
||||
}
|
||||
}
|
||||
|
||||
if len(allPairs) != len(associatedAssets) {
|
||||
return nil, fmt.Errorf("%s generate default subscriptions: pair and asset type len mismatch", b.Name)
|
||||
}
|
||||
|
||||
channels := []string{bitmexWSOrderbookL2, bitmexWSTrade}
|
||||
subscriptions := []wshandler.WebsocketChannelSubscription{
|
||||
subscriptions := []stream.ChannelSubscription{
|
||||
{
|
||||
Channel: bitmexWSAnnouncement,
|
||||
},
|
||||
@@ -561,25 +574,29 @@ func (b *Bitmex) GenerateDefaultSubscriptions() {
|
||||
|
||||
for i := range channels {
|
||||
for j := range allPairs {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i] + ":" + allPairs[j].String(),
|
||||
Currency: allPairs[j],
|
||||
Asset: associatedAssets[j],
|
||||
})
|
||||
}
|
||||
}
|
||||
b.Websocket.SubscribeToChannels(subscriptions)
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// GenerateAuthenticatedSubscriptions Adds authenticated subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *Bitmex) GenerateAuthenticatedSubscriptions() {
|
||||
func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
if !b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
return
|
||||
return nil, nil
|
||||
}
|
||||
contracts, err := b.GetEnabledPairs(asset.PerpetualContract)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contracts := b.GetEnabledPairs(asset.PerpetualContract)
|
||||
channels := []string{bitmexWSExecution,
|
||||
bitmexWSPosition,
|
||||
}
|
||||
subscriptions := []wshandler.WebsocketChannelSubscription{
|
||||
subscriptions := []stream.ChannelSubscription{
|
||||
{
|
||||
Channel: bitmexWSAffiliate,
|
||||
},
|
||||
@@ -601,31 +618,48 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() {
|
||||
}
|
||||
for i := range channels {
|
||||
for j := range contracts {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i] + ":" + contracts[j].String(),
|
||||
Currency: contracts[j],
|
||||
Asset: asset.PerpetualContract,
|
||||
})
|
||||
}
|
||||
}
|
||||
b.Websocket.SubscribeToChannels(subscriptions)
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a websocket channel
|
||||
func (b *Bitmex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
func (b *Bitmex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
var subscriber WebsocketRequest
|
||||
subscriber.Command = "subscribe"
|
||||
subscriber.Arguments = append(subscriber.Arguments, channelToSubscribe.Channel)
|
||||
return b.WebsocketConn.SendJSONMessage(subscriber)
|
||||
|
||||
for i := range channelsToSubscribe {
|
||||
subscriber.Arguments = append(subscriber.Arguments,
|
||||
channelsToSubscribe[i].Channel)
|
||||
}
|
||||
err := b.Websocket.Conn.SendJSONMessage(subscriber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (b *Bitmex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
var subscriber WebsocketRequest
|
||||
subscriber.Command = "unsubscribe"
|
||||
subscriber.Arguments = append(subscriber.Arguments,
|
||||
channelToSubscribe.Params["args"],
|
||||
channelToSubscribe.Channel+":"+channelToSubscribe.Currency.String())
|
||||
return b.WebsocketConn.SendJSONMessage(subscriber)
|
||||
func (b *Bitmex) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
var unsubscriber WebsocketRequest
|
||||
unsubscriber.Command = "unsubscribe"
|
||||
|
||||
for i := range channelsToUnsubscribe {
|
||||
unsubscriber.Arguments = append(unsubscriber.Arguments,
|
||||
channelsToUnsubscribe[i].Channel)
|
||||
}
|
||||
err := b.Websocket.Conn.SendJSONMessage(unsubscriber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebsocketSendAuth sends an authenticated subscription
|
||||
@@ -645,7 +679,7 @@ func (b *Bitmex) websocketSendAuth() error {
|
||||
sendAuth.Command = "authKeyExpires"
|
||||
sendAuth.Arguments = append(sendAuth.Arguments, b.API.Credentials.Key, timestamp,
|
||||
signature)
|
||||
err := b.WebsocketConn.SendJSONMessage(sendAuth)
|
||||
err := b.Websocket.Conn.SendJSONMessage(sendAuth)
|
||||
if err != nil {
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
return err
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"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/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -55,41 +55,17 @@ func (b *Bitmex) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresKey = true
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.PerpetualContract,
|
||||
asset.Futures,
|
||||
asset.DownsideProfitContract,
|
||||
asset.UpsideProfitContract,
|
||||
},
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
err := b.SetGlobalPairsManager(requestFmt,
|
||||
configFmt,
|
||||
asset.PerpetualContract,
|
||||
asset.Futures,
|
||||
asset.Index)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
// Same format used for perpetual contracts and futures
|
||||
fmt1 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
}
|
||||
b.CurrencyPairs.Store(asset.PerpetualContract, fmt1)
|
||||
b.CurrencyPairs.Store(asset.Futures, fmt1)
|
||||
|
||||
// Upside and Downside profit contracts use the same format
|
||||
fmt2 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Delimiter: "_",
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Delimiter: "_",
|
||||
Uppercase: true,
|
||||
},
|
||||
}
|
||||
b.CurrencyPairs.Store(asset.DownsideProfitContract, fmt2)
|
||||
b.CurrencyPairs.Store(asset.UpsideProfitContract, fmt2)
|
||||
|
||||
b.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
REST: true,
|
||||
@@ -144,7 +120,7 @@ func (b *Bitmex) SetDefaults() {
|
||||
b.API.Endpoints.URLDefault = bitmexAPIURL
|
||||
b.API.Endpoints.URL = b.API.Endpoints.URLDefault
|
||||
b.API.Endpoints.WebsocketURL = bitmexWSURL
|
||||
b.Websocket = wshandler.New()
|
||||
b.Websocket = stream.New()
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
@@ -162,41 +138,30 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Websocket.Setup(
|
||||
&wshandler.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: bitmexWSURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
err = b.Websocket.Setup(&stream.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: bitmexWSURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
GenerateSubscriptions: b.GenerateDefaultSubscriptions,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit,
|
||||
UpdateEntriesByID: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: b.Websocket.GetWebsocketURL(),
|
||||
ProxyURL: b.Websocket.GetProxyAddress(),
|
||||
Verbose: b.Verbose,
|
||||
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
exch.Name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Start starts the Bitmex go routine
|
||||
@@ -211,7 +176,11 @@ func (b *Bitmex) Start(wg *sync.WaitGroup) {
|
||||
// Run implements the Bitmex wrapper
|
||||
func (b *Bitmex) Run() {
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", b.Name, common.IsEnabled(b.Websocket.IsEnabled()), b.API.Endpoints.WebsocketURL)
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Websocket: %s. (url: %s).\n",
|
||||
b.Name,
|
||||
common.IsEnabled(b.Websocket.IsEnabled()),
|
||||
b.API.Endpoints.WebsocketURL)
|
||||
b.PrintEnabledPairs()
|
||||
}
|
||||
|
||||
@@ -226,8 +195,8 @@ func (b *Bitmex) Run() {
|
||||
}
|
||||
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (b *Bitmex) FetchTradablePairs(_ asset.Item) ([]string, error) {
|
||||
marketInfo, err := b.GetActiveInstruments(&GenericRequestParams{})
|
||||
func (b *Bitmex) FetchTradablePairs(asset asset.Item) ([]string, error) {
|
||||
marketInfo, err := b.GetActiveAndIndexInstruments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -248,72 +217,78 @@ func (b *Bitmex) UpdateTradablePairs(forceUpdate bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var assetPairs []string
|
||||
for x := range b.CurrencyPairs.AssetTypes {
|
||||
switch b.CurrencyPairs.AssetTypes[x] {
|
||||
case asset.PerpetualContract:
|
||||
for y := range pairs {
|
||||
if strings.Contains(pairs[y], "USD") {
|
||||
assetPairs = append(assetPairs, pairs[y])
|
||||
}
|
||||
}
|
||||
case asset.Futures:
|
||||
for y := range pairs {
|
||||
if strings.Contains(pairs[y], "20") {
|
||||
assetPairs = append(assetPairs, pairs[y])
|
||||
}
|
||||
}
|
||||
case asset.DownsideProfitContract:
|
||||
for y := range pairs {
|
||||
if strings.Contains(pairs[y], "_D") {
|
||||
assetPairs = append(assetPairs, pairs[y])
|
||||
}
|
||||
}
|
||||
case asset.UpsideProfitContract:
|
||||
for y := range pairs {
|
||||
if strings.Contains(pairs[y], "_U") {
|
||||
assetPairs = append(assetPairs, pairs[y])
|
||||
}
|
||||
}
|
||||
// Zerovalue current list which will remove old asset pairs when contract
|
||||
// types expire or become obsolete
|
||||
var assetPairs = map[asset.Item][]string{
|
||||
asset.Index: {},
|
||||
asset.PerpetualContract: {},
|
||||
asset.Futures: {},
|
||||
}
|
||||
|
||||
for x := range pairs {
|
||||
if strings.Contains(pairs[x], ".") {
|
||||
assetPairs[asset.Index] = append(assetPairs[asset.Index], pairs[x])
|
||||
continue
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(currency.NewPairsFromStrings(assetPairs), b.CurrencyPairs.AssetTypes[x], false, false)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s failed to update available pairs. Err: %v", b.Name, err)
|
||||
if strings.Contains(pairs[x], "USD") {
|
||||
assetPairs[asset.PerpetualContract] = append(assetPairs[asset.PerpetualContract],
|
||||
pairs[x])
|
||||
continue
|
||||
}
|
||||
assetPairs = nil
|
||||
|
||||
assetPairs[asset.Futures] = append(assetPairs[asset.Futures], pairs[x])
|
||||
}
|
||||
|
||||
for a, values := range assetPairs {
|
||||
p, err := currency.NewPairsFromStrings(values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(p, a, false, false)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s failed to update available pairs. Err: %v",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *Bitmex) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerPrice := new(ticker.Price)
|
||||
tick, err := b.GetActiveInstruments(&GenericRequestParams{})
|
||||
tick, err := b.GetActiveAndIndexInstruments()
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
pairs := b.GetEnabledPairs(assetType)
|
||||
for i := range pairs {
|
||||
for j := range tick {
|
||||
if !pairs[i].Equal(tick[j].Symbol) {
|
||||
continue
|
||||
}
|
||||
tickerPrice = &ticker.Price{
|
||||
Last: tick[j].LastPrice,
|
||||
High: tick[j].HighPrice,
|
||||
Low: tick[j].LowPrice,
|
||||
Bid: tick[j].BidPrice,
|
||||
Ask: tick[j].AskPrice,
|
||||
Volume: tick[j].Volume24h,
|
||||
Close: tick[j].PrevClosePrice,
|
||||
Pair: tick[j].Symbol,
|
||||
LastUpdated: tick[j].Timestamp,
|
||||
}
|
||||
err = ticker.ProcessTicker(b.Name, tickerPrice, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
|
||||
pairs, err := b.GetEnabledPairs(assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for j := range tick {
|
||||
if !pairs.Contains(tick[j].Symbol, true) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Last: tick[j].LastPrice,
|
||||
High: tick[j].HighPrice,
|
||||
Low: tick[j].LowPrice,
|
||||
Bid: tick[j].BidPrice,
|
||||
Ask: tick[j].AskPrice,
|
||||
Volume: tick[j].Volume24h,
|
||||
Close: tick[j].PrevClosePrice,
|
||||
Pair: tick[j].Symbol,
|
||||
LastUpdated: tick[j].Timestamp,
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ticker.GetTicker(b.Name, p, assetType)
|
||||
@@ -339,25 +314,34 @@ func (b *Bitmex) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbo
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
orderBook := new(orderbook.Base)
|
||||
|
||||
orderbookNew, err := b.GetOrderbook(OrderBookGetL2Params{
|
||||
Symbol: b.FormatExchangeCurrency(p, assetType).String(),
|
||||
Depth: 500})
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
if assetType == asset.Index {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
for _, ob := range orderbookNew {
|
||||
if strings.EqualFold(ob.Side, order.Sell.String()) {
|
||||
orderBook.Asks = append(orderBook.Asks,
|
||||
orderbook.Item{Amount: float64(ob.Size), Price: ob.Price})
|
||||
fpair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderbookNew, err := b.GetOrderbook(OrderBookGetL2Params{
|
||||
Symbol: fpair.String(),
|
||||
Depth: 500})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBook := new(orderbook.Base)
|
||||
for i := range orderbookNew {
|
||||
if strings.EqualFold(orderbookNew[i].Side, order.Sell.String()) {
|
||||
orderBook.Asks = append(orderBook.Asks, orderbook.Item{
|
||||
Amount: float64(orderbookNew[i].Size),
|
||||
Price: orderbookNew[i].Price})
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(ob.Side, order.Buy.String()) {
|
||||
orderBook.Bids = append(orderBook.Bids,
|
||||
orderbook.Item{Amount: float64(ob.Size), Price: ob.Price})
|
||||
continue
|
||||
if strings.EqualFold(orderbookNew[i].Side, order.Buy.String()) {
|
||||
orderBook.Bids = append(orderBook.Bids, orderbook.Item{
|
||||
Amount: float64(orderbookNew[i].Size),
|
||||
Price: orderbookNew[i].Price})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,11 +545,6 @@ func (b *Bitmex) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *Bitmex) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return b.Websocket, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (b *Bitmex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if !b.AllowAuthenticatedRequest() && // Todo check connection status
|
||||
@@ -587,6 +566,11 @@ func (b *Bitmex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.PerpetualContract, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range resp {
|
||||
orderSide := orderSideMap[resp[i].Side]
|
||||
orderType := orderTypeMap[resp[i].OrdType]
|
||||
@@ -604,7 +588,7 @@ func (b *Bitmex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
Status: order.Status(resp[i].OrdStatus),
|
||||
Pair: currency.NewPairWithDelimiter(resp[i].Symbol,
|
||||
resp[i].SettlCurrency,
|
||||
b.GetPairFormat(asset.PerpetualContract, false).Delimiter),
|
||||
format.Delimiter),
|
||||
}
|
||||
|
||||
orders = append(orders, orderDetail)
|
||||
@@ -628,6 +612,11 @@ func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.PerpetualContract, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range resp {
|
||||
orderSide := orderSideMap[resp[i].Side]
|
||||
orderType := orderTypeMap[resp[i].OrdType]
|
||||
@@ -645,7 +634,7 @@ func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
Status: order.Status(resp[i].OrdStatus),
|
||||
Pair: currency.NewPairWithDelimiter(resp[i].Symbol,
|
||||
resp[i].SettlCurrency,
|
||||
b.GetPairFormat(asset.PerpetualContract, false).Delimiter),
|
||||
format.Delimiter),
|
||||
}
|
||||
|
||||
orders = append(orders, orderDetail)
|
||||
@@ -658,25 +647,6 @@ func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *Bitmex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
b.Websocket.SubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *Bitmex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
b.Websocket.RemoveSubscribedChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *Bitmex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return b.Websocket.GetSubscriptions(), nil
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *Bitmex) AuthenticateWebsocket() error {
|
||||
return b.websocketSendAuth()
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
@@ -64,7 +63,6 @@ const (
|
||||
// Bitstamp is the overarching type across the bitstamp package
|
||||
type Bitstamp struct {
|
||||
exchange.Base
|
||||
WebsocketConn *wshandler.WebsocketConnection
|
||||
}
|
||||
|
||||
// GetFee returns an estimate of fee based on type of transaction
|
||||
|
||||
@@ -30,12 +30,11 @@ func TestMain(m *testing.M) {
|
||||
bitstampConfig.API.Credentials.Secret = apiSecret
|
||||
bitstampConfig.API.Credentials.ClientID = customerID
|
||||
b.SetDefaults()
|
||||
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = b.Setup(bitstampConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Bitstamp setup error", err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
log.Printf(sharedtestvalues.LiveTesting, b.Name, b.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ func TestMain(m *testing.M) {
|
||||
bitstampConfig.API.Credentials.Secret = apiSecret
|
||||
bitstampConfig.API.Credentials.ClientID = customerID
|
||||
b.SetDefaults()
|
||||
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = b.Setup(bitstampConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Bitstamp setup error", err)
|
||||
@@ -46,8 +47,6 @@ func TestMain(m *testing.M) {
|
||||
|
||||
b.HTTPClient = newClient
|
||||
b.API.Endpoints.URL = serverDetails + "/api"
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
log.Printf(sharedtestvalues.MockTesting, b.Name, b.API.Endpoints.URL)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -678,21 +678,27 @@ func TestBitstamp_OHLC(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBitstamp_GetHistoricCandles(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("btcusd")
|
||||
currencyPair, err := currency.NewPairFromString("btcusd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
start := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
|
||||
_, err := b.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay)
|
||||
_, err = b.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBitstamp_GetHistoricCandlesExtended(t *testing.T) {
|
||||
currencyPair := currency.NewPairFromString("btcusd")
|
||||
currencyPair, err := currency.NewPairFromString("btcusd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
start := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
_, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneDay)
|
||||
_, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
@@ -24,10 +25,10 @@ const (
|
||||
// WsConnect connects to a websocket feed
|
||||
func (b *Bitstamp) WsConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
return errors.New(stream.WebsocketNotEnabled)
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := b.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
err := b.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -38,33 +39,27 @@ func (b *Bitstamp) WsConnect() error {
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
b.generateDefaultSubscriptions()
|
||||
subs, err := b.generateDefaultSubscriptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go b.wsReadData()
|
||||
|
||||
return nil
|
||||
return b.Websocket.SubscribeToChannels(subs)
|
||||
}
|
||||
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *Bitstamp) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
defer b.Websocket.Wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
resp := b.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
default:
|
||||
resp, err := b.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
b.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
err := b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +92,11 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error {
|
||||
return err
|
||||
}
|
||||
currencyPair := strings.Split(wsResponse.Channel, "_")
|
||||
p := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
p, err := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -109,7 +108,11 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error {
|
||||
return err
|
||||
}
|
||||
currencyPair := strings.Split(wsResponse.Channel, "_")
|
||||
p := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
p, err := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
side := order.Buy
|
||||
if wsTradeTemp.Data.Type == -1 {
|
||||
side = order.Sell
|
||||
@@ -119,7 +122,7 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
b.Websocket.DataHandler <- stream.TradeData{
|
||||
Timestamp: time.Unix(wsTradeTemp.Data.Timestamp, 0),
|
||||
CurrencyPair: p,
|
||||
AssetType: a,
|
||||
@@ -134,45 +137,73 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket order acknowledgement", b.Name)
|
||||
}
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitstamp) generateDefaultSubscriptions() {
|
||||
func (b *Bitstamp) generateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var channels = []string{"live_trades_", "order_book_"}
|
||||
enabledCurrencies := b.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
enabledCurrencies, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
for i := range channels {
|
||||
for j := range enabledCurrencies {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i] + enabledCurrencies[j].Lower().String(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
b.Websocket.SubscribeToChannels(subscriptions)
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *Bitstamp) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
req := websocketEventRequest{
|
||||
Event: "bts:subscribe",
|
||||
Data: websocketData{
|
||||
Channel: channelToSubscribe.Channel,
|
||||
},
|
||||
func (b *Bitstamp) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
var errs common.Errors
|
||||
for i := range channelsToSubscribe {
|
||||
req := websocketEventRequest{
|
||||
Event: "bts:subscribe",
|
||||
Data: websocketData{
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
},
|
||||
}
|
||||
err := b.Websocket.Conn.SendJSONMessage(req)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
|
||||
}
|
||||
return b.WebsocketConn.SendJSONMessage(req)
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (b *Bitstamp) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
req := websocketEventRequest{
|
||||
Event: "bts:unsubscribe",
|
||||
Data: websocketData{
|
||||
Channel: channelToSubscribe.Channel,
|
||||
},
|
||||
func (b *Bitstamp) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
var errs common.Errors
|
||||
for i := range channelsToUnsubscribe {
|
||||
req := websocketEventRequest{
|
||||
Event: "bts:unsubscribe",
|
||||
Data: websocketData{
|
||||
Channel: channelsToUnsubscribe[i].Channel,
|
||||
},
|
||||
}
|
||||
err := b.Websocket.Conn.SendJSONMessage(req)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
|
||||
}
|
||||
return b.WebsocketConn.SendJSONMessage(req)
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, assetType asset.Item) error {
|
||||
@@ -207,29 +238,22 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair,
|
||||
|
||||
bids = append(bids, orderbook.Item{Price: target, Amount: amount})
|
||||
}
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
|
||||
return b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: p,
|
||||
LastUpdated: time.Unix(update.Timestamp, 0),
|
||||
AssetType: asset.Spot,
|
||||
AssetType: assetType,
|
||||
ExchangeName: b.Name,
|
||||
})
|
||||
}
|
||||
|
||||
func (b *Bitstamp) seedOrderBook() error {
|
||||
p, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: assetType,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitstamp) seedOrderBook() error {
|
||||
p := b.GetEnabledPairs(asset.Spot)
|
||||
for x := range p {
|
||||
orderbookSeed, err := b.GetOrderbook(p[x].String())
|
||||
if err != nil {
|
||||
@@ -257,12 +281,6 @@ func (b *Bitstamp) seedOrderBook() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p[x],
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"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/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -56,17 +56,11 @@ func (b *Bitstamp) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
b.API.CredentialsValidator.RequiresClientID = true
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
b.Features = exchange.Features{
|
||||
@@ -137,7 +131,7 @@ func (b *Bitstamp) SetDefaults() {
|
||||
b.API.Endpoints.URLDefault = bitstampAPIURL
|
||||
b.API.Endpoints.URL = b.API.Endpoints.URLDefault
|
||||
b.API.Endpoints.WebsocketURL = bitstampWSURL
|
||||
b.Websocket = wshandler.New()
|
||||
b.Websocket = stream.New()
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
@@ -155,34 +149,30 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Websocket.Setup(
|
||||
&wshandler.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: bitstampWSURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
err = b.Websocket.Setup(&stream.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: bitstampWSURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
GenerateSubscriptions: b.generateDefaultSubscriptions,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
URL: b.Websocket.GetWebsocketURL(),
|
||||
ProxyURL: b.Websocket.GetProxyAddress(),
|
||||
Verbose: b.Verbose,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Start starts the Bitstamp go routine
|
||||
@@ -245,32 +235,35 @@ func (b *Bitstamp) UpdateTradablePairs(forceUpdate bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate)
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(p, asset.Spot, false, forceUpdate)
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerPrice := new(ticker.Price)
|
||||
tick, err := b.GetTicker(p.String(), false)
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tickerPrice = &ticker.Price{
|
||||
Last: tick.Last,
|
||||
High: tick.High,
|
||||
Low: tick.Low,
|
||||
Bid: tick.Bid,
|
||||
Ask: tick.Ask,
|
||||
Volume: tick.Volume,
|
||||
Open: tick.Open,
|
||||
Pair: p,
|
||||
LastUpdated: time.Unix(tick.Timestamp, 0),
|
||||
}
|
||||
|
||||
err = ticker.ProcessTicker(b.Name, tickerPrice, assetType)
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Last: tick.Last,
|
||||
High: tick.High,
|
||||
Low: tick.Low,
|
||||
Bid: tick.Bid,
|
||||
Ask: tick.Ask,
|
||||
Volume: tick.Volume,
|
||||
Open: tick.Open,
|
||||
Pair: p,
|
||||
LastUpdated: time.Unix(tick.Timestamp, 0),
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType})
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ticker.GetTicker(b.Name, p, assetType)
|
||||
@@ -548,11 +541,6 @@ func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdra
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *Bitstamp) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return b.Websocket, nil
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var currPair string
|
||||
@@ -580,6 +568,11 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
"%s GetActiveOrders unable to parse time: %s\n", b.Name, err)
|
||||
}
|
||||
|
||||
pair, err := currency.NewPairFromString(resp[i].Currency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
Amount: resp[i].Amount,
|
||||
ID: strconv.FormatInt(resp[i].ID, 10),
|
||||
@@ -587,7 +580,7 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
Type: order.Limit,
|
||||
Side: orderSide,
|
||||
Date: tm,
|
||||
Pair: currency.NewPairFromString(resp[i].Currency),
|
||||
Pair: pair,
|
||||
Exchange: b.Name,
|
||||
})
|
||||
}
|
||||
@@ -604,6 +597,12 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
if len(req.Pairs) == 1 {
|
||||
currPair = req.Pairs[0].String()
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := b.GetUserTransactions(currPair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -644,7 +643,7 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
if quoteCurrency.String() != "" && baseCurrency.String() != "" {
|
||||
currPair = currency.NewPairWithDelimiter(baseCurrency.String(),
|
||||
quoteCurrency.String(),
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
format.Delimiter)
|
||||
}
|
||||
|
||||
tm, err := parseTime(resp[i].Date)
|
||||
@@ -666,30 +665,6 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *Bitstamp) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
b.Websocket.SubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *Bitstamp) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
b.Websocket.RemoveSubscribedChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *Bitstamp) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return b.Websocket.GetSubscriptions(), nil
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *Bitstamp) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (b *Bitstamp) ValidateCredentials() error {
|
||||
@@ -712,8 +687,13 @@ func (b *Bitstamp) GetHistoricCandles(pair currency.Pair, a asset.Item, start, e
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
formattedPair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
candles, err := b.OHLC(
|
||||
b.FormatExchangeCurrency(pair, a).Lower().String(),
|
||||
formattedPair.Lower().String(),
|
||||
start,
|
||||
end,
|
||||
b.FormatExchangeKlineInterval(interval),
|
||||
@@ -759,9 +739,14 @@ func (b *Bitstamp) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item,
|
||||
}
|
||||
|
||||
dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
formattedPair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
for x := range dates {
|
||||
candles, err := b.OHLC(
|
||||
b.FormatExchangeCurrency(pair, a).Lower().String(),
|
||||
formattedPair.Lower().String(),
|
||||
dates[x].Start,
|
||||
dates[x].End,
|
||||
b.FormatExchangeKlineInterval(interval),
|
||||
|
||||
@@ -343,14 +343,19 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
p, err := currency.NewPairFromString(currPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
Type: order.AnyType,
|
||||
Pairs: []currency.Pair{currency.NewPairFromString(currPair)},
|
||||
Pairs: []currency.Pair{p},
|
||||
}
|
||||
|
||||
getOrdersRequest.Pairs[0].Delimiter = "-"
|
||||
|
||||
_, err := b.GetActiveOrders(&getOrdersRequest)
|
||||
_, err = b.GetActiveOrders(&getOrdersRequest)
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
t.Errorf("Could not get open orders: %s", err)
|
||||
} else if !areTestAPIKeysSet() && err == nil {
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -54,19 +53,11 @@ func (b *Bittrex) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresKey = true
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Delimiter: "-",
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Delimiter: "-",
|
||||
Uppercase: true,
|
||||
},
|
||||
requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true}
|
||||
err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
b.Features = exchange.Features{
|
||||
@@ -113,7 +104,6 @@ func (b *Bittrex) Setup(exch *config.ExchangeConfig) error {
|
||||
b.SetEnabled(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
return b.SetupDefaults(exch)
|
||||
}
|
||||
|
||||
@@ -133,24 +123,53 @@ func (b *Bittrex) Run() {
|
||||
}
|
||||
|
||||
forceUpdate := false
|
||||
delim := b.GetPairFormat(asset.Spot, false).Delimiter
|
||||
if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), delim) ||
|
||||
!common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), delim) {
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return
|
||||
}
|
||||
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return
|
||||
}
|
||||
|
||||
avail, err := b.GetAvailablePairs(asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return
|
||||
}
|
||||
|
||||
if !common.StringDataContains(pairs.Strings(), format.Delimiter) ||
|
||||
!common.StringDataContains(avail.Strings(), format.Delimiter) {
|
||||
forceUpdate = true
|
||||
log.Warn(log.ExchangeSys, "Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again")
|
||||
|
||||
err := b.UpdatePairs(currency.NewPairsFromStrings(
|
||||
[]string{currency.USDT.String() + delim + currency.BTC.String()},
|
||||
),
|
||||
asset.Spot,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
pairs, err = currency.NewPairsFromStrings([]string{currency.USDT.String() +
|
||||
format.Delimiter +
|
||||
currency.BTC.String()})
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
} else {
|
||||
err = b.UpdatePairs(pairs, asset.Spot, true, true)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +177,7 @@ func (b *Bittrex) Run() {
|
||||
return
|
||||
}
|
||||
|
||||
err := b.UpdateTradablePairs(forceUpdate)
|
||||
err = b.UpdateTradablePairs(forceUpdate)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update tradable pairs. Err: %s",
|
||||
@@ -192,8 +211,12 @@ func (b *Bittrex) UpdateTradablePairs(forceUpdate bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate)
|
||||
return b.UpdatePairs(p, asset.Spot, false, forceUpdate)
|
||||
}
|
||||
|
||||
// UpdateAccountInfo Retrieves balances for all enabled currencies for the
|
||||
@@ -239,41 +262,46 @@ func (b *Bittrex) FetchAccountInfo() (account.Holdings, error) {
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *Bittrex) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerPrice := new(ticker.Price)
|
||||
ticks, err := b.GetMarketSummaries()
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
}
|
||||
pairs := b.GetEnabledPairs(assetType)
|
||||
for i := range pairs {
|
||||
for j := range ticks.Result {
|
||||
if !strings.EqualFold(ticks.Result[j].MarketName, pairs[i].String()) {
|
||||
continue
|
||||
}
|
||||
tickerTime, err := parseTime(ticks.Result[j].TimeStamp)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s UpdateTicker unable to parse time: %s\n", b.Name, err)
|
||||
}
|
||||
tickerPrice = &ticker.Price{
|
||||
Last: ticks.Result[j].Last,
|
||||
High: ticks.Result[j].High,
|
||||
Low: ticks.Result[j].Low,
|
||||
Bid: ticks.Result[j].Bid,
|
||||
Ask: ticks.Result[j].Ask,
|
||||
Volume: ticks.Result[j].BaseVolume,
|
||||
QuoteVolume: ticks.Result[j].Volume,
|
||||
Close: ticks.Result[j].PrevDay,
|
||||
Pair: pairs[i],
|
||||
LastUpdated: tickerTime,
|
||||
}
|
||||
err = ticker.ProcessTicker(b.Name, tickerPrice, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pairs, err := b.GetEnabledPairs(assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for j := range ticks.Result {
|
||||
cp, err := currency.NewPairFromString(ticks.Result[j].MarketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !pairs.Contains(cp, true) {
|
||||
continue
|
||||
}
|
||||
tickerTime, err := parseTime(ticks.Result[j].TimeStamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Last: ticks.Result[j].Last,
|
||||
High: ticks.Result[j].High,
|
||||
Low: ticks.Result[j].Low,
|
||||
Bid: ticks.Result[j].Bid,
|
||||
Ask: ticks.Result[j].Ask,
|
||||
Volume: ticks.Result[j].BaseVolume,
|
||||
QuoteVolume: ticks.Result[j].Volume,
|
||||
Close: ticks.Result[j].PrevDay,
|
||||
Pair: cp,
|
||||
LastUpdated: tickerTime,
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ticker.GetTicker(b.Name, p, assetType)
|
||||
}
|
||||
|
||||
@@ -297,12 +325,17 @@ func (b *Bittrex) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderb
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (b *Bittrex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
orderBook := new(orderbook.Base)
|
||||
orderbookNew, err := b.GetOrderbook(b.FormatExchangeCurrency(p, assetType).String())
|
||||
fpair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderbookNew, err := b.GetOrderbook(fpair.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBook := new(orderbook.Base)
|
||||
for x := range orderbookNew.Result.Buy {
|
||||
orderBook.Bids = append(orderBook.Bids,
|
||||
orderbook.Item{
|
||||
@@ -453,11 +486,6 @@ func (b *Bittrex) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *Bittrex) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (b *Bittrex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if !b.AllowAuthenticatedRequest() && // Todo check connection status
|
||||
@@ -474,6 +502,11 @@ func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
currPair = req.Pairs[0].String()
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := b.GetOpenOrders(currPair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -491,8 +524,16 @@ func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
resp.Result[i].Opened)
|
||||
}
|
||||
|
||||
pair := currency.NewPairDelimiter(resp.Result[i].Exchange,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
pair, err := currency.NewPairDelimiter(resp.Result[i].Exchange,
|
||||
format.Delimiter)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Exchange %v Func %v Order %v Could not parse currency pair %v",
|
||||
b.Name,
|
||||
"GetActiveOrders",
|
||||
resp.Result[i].OrderUUID,
|
||||
err)
|
||||
}
|
||||
orderType := order.Type(strings.ToUpper(resp.Result[i].Type))
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
@@ -521,6 +562,11 @@ func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
currPair = req.Pairs[0].String()
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := b.GetOrderHistoryForCurrency(currPair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -533,13 +579,21 @@ func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Exchange %v Func %v Order %v Could not parse date to unix with value of %v",
|
||||
b.Name,
|
||||
"GetActiveOrders",
|
||||
"GetOrderHistory",
|
||||
resp.Result[i].OrderUUID,
|
||||
resp.Result[i].Opened)
|
||||
}
|
||||
|
||||
pair := currency.NewPairDelimiter(resp.Result[i].Exchange,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
pair, err := currency.NewPairDelimiter(resp.Result[i].Exchange,
|
||||
format.Delimiter)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Exchange %v Func %v Order %v Could not parse currency pair %v",
|
||||
b.Name,
|
||||
"GetOrderHistory",
|
||||
resp.Result[i].OrderUUID,
|
||||
err)
|
||||
}
|
||||
orderType := order.Type(strings.ToUpper(resp.Result[i].Type))
|
||||
|
||||
orders = append(orders, order.Detail{
|
||||
@@ -561,28 +615,6 @@ func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail,
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *Bittrex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *Bittrex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *Bittrex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *Bittrex) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (b *Bittrex) ValidateCredentials() error {
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -72,13 +71,12 @@ const (
|
||||
heartbeat = "heartbeat"
|
||||
tick = "tick"
|
||||
wsOB = "orderbookUpdate"
|
||||
tradeEndPoint = "tradeEndPoint"
|
||||
tradeEndPoint = "trade"
|
||||
)
|
||||
|
||||
// BTCMarkets is the overarching type across the BTCMarkets package
|
||||
type BTCMarkets struct {
|
||||
exchange.Base
|
||||
WebsocketConn *wshandler.WebsocketConnection
|
||||
}
|
||||
|
||||
// GetMarkets returns the BTCMarkets instruments
|
||||
@@ -191,7 +189,7 @@ func (b *BTCMarkets) GetMarketCandles(marketID, timeWindow string, from, to time
|
||||
}
|
||||
|
||||
// GetTickers gets multiple tickers
|
||||
func (b *BTCMarkets) GetTickers(marketIDs []currency.Pair) ([]Ticker, error) {
|
||||
func (b *BTCMarkets) GetTickers(marketIDs currency.Pairs) ([]Ticker, error) {
|
||||
var tickers []Ticker
|
||||
params := url.Values{}
|
||||
for x := range marketIDs {
|
||||
@@ -761,7 +759,11 @@ func (b *BTCMarkets) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
return fee, err
|
||||
}
|
||||
for x := range temp.FeeByMarkets {
|
||||
if currency.NewPairFromString(temp.FeeByMarkets[x].MarketID) == feeBuilder.Pair {
|
||||
p, err := currency.NewPairFromString(temp.FeeByMarkets[x].MarketID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if p == feeBuilder.Pair {
|
||||
fee = temp.FeeByMarkets[x].MakerFeeRate
|
||||
if !feeBuilder.IsMaker {
|
||||
fee = temp.FeeByMarkets[x].TakerFeeRate
|
||||
|
||||
@@ -44,21 +44,17 @@ func TestMain(m *testing.M) {
|
||||
bConfig.API.Credentials.Key = apiKey
|
||||
bConfig.API.Credentials.Secret = apiSecret
|
||||
bConfig.API.AuthenticatedSupport = true
|
||||
|
||||
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = b.Setup(bConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
|
||||
err = b.ValidateCredentials()
|
||||
if err != nil {
|
||||
fmt.Println("API credentials are invalid:", err)
|
||||
b.API.AuthenticatedSupport = false
|
||||
b.API.AuthenticatedWebsocketSupport = false
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -108,8 +104,11 @@ func TestGetMarketCandles(t *testing.T) {
|
||||
|
||||
func TestGetTickers(t *testing.T) {
|
||||
t.Parallel()
|
||||
temp := currency.NewPairsFromStrings([]string{LTCAUD, BTCAUD})
|
||||
_, err := b.GetTickers(temp)
|
||||
temp, err := currency.NewPairsFromStrings([]string{LTCAUD, BTCAUD})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetTickers(temp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -720,8 +719,11 @@ func TestWsOrders(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBTCMarkets_GetHistoricCandles(t *testing.T) {
|
||||
p := currency.NewPairFromString(BTCAUD)
|
||||
_, err := b.GetHistoricCandles(p, asset.Spot, time.Now().Add(-time.Hour*24).UTC(), time.Now().UTC(), kline.OneHour)
|
||||
p, err := currency.NewPairFromString(BTCAUD)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetHistoricCandles(p, asset.Spot, time.Now().Add(-time.Hour*24).UTC(), time.Now().UTC(), kline.OneHour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -736,8 +738,11 @@ func TestBTCMarkets_GetHistoricCandles(t *testing.T) {
|
||||
func TestBTCMarkets_GetHistoricCandlesExtended(t *testing.T) {
|
||||
start := time.Now().AddDate(0, 0, -1001)
|
||||
end := time.Now()
|
||||
p := currency.NewPairFromString(BTCAUD)
|
||||
_, err := b.GetHistoricCandlesExtended(p, asset.Spot, start, end, kline.OneDay)
|
||||
p, err := currency.NewPairFromString(BTCAUD)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetHistoricCandlesExtended(p, asset.Spot, start, end, kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -337,21 +337,14 @@ type TradingFeeResponse struct {
|
||||
FeeByMarkets []TradingFeeData `json:"FeeByMarkets"`
|
||||
}
|
||||
|
||||
// WsSubscribe message sent via ws to subscribe
|
||||
// WsSubscribe defines a subscription message used in the Subscribe function
|
||||
type WsSubscribe struct {
|
||||
MarketIDs []string `json:"marketIds,omitempty"`
|
||||
Channels []string `json:"channels"`
|
||||
MessageType string `json:"messageType"`
|
||||
}
|
||||
|
||||
// WsAuthSubscribe message sent via login to subscribe
|
||||
type WsAuthSubscribe struct {
|
||||
MarketIDs []string `json:"marketIds,omitempty"`
|
||||
Channels []string `json:"channels"`
|
||||
Key string `json:"key"`
|
||||
Signature string `json:"signature"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
MessageType string `json:"messageType"`
|
||||
Channels []string `json:"channels,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Signature string `json:"signature,omitempty"`
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
MessageType string `json:"messageType,omitempty"`
|
||||
}
|
||||
|
||||
// WsMessageType message sent via ws to determine type
|
||||
@@ -380,6 +373,7 @@ type WsTrade struct {
|
||||
TradeID int64 `json:"tradeId"`
|
||||
Price float64 `json:"price,string"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
Side string `json:"side"`
|
||||
MessageType string `json:"messageType"`
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,12 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"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/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
@@ -29,10 +28,10 @@ const (
|
||||
// WsConnect connects to a websocket feed
|
||||
func (b *BTCMarkets) WsConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
return errors.New(stream.WebsocketNotEnabled)
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := b.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
err := b.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -40,35 +39,26 @@ func (b *BTCMarkets) WsConnect() error {
|
||||
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name)
|
||||
}
|
||||
go b.wsReadData()
|
||||
if b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
b.createChannels()
|
||||
subs, err := b.generateDefaultSubscriptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.generateDefaultSubscriptions()
|
||||
return nil
|
||||
return b.Websocket.SubscribeToChannels(subs)
|
||||
}
|
||||
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *BTCMarkets) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
defer b.Websocket.Wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
resp := b.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
default:
|
||||
resp, err := b.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
b.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
err := b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +81,11 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(ob.Currency)
|
||||
p, err := currency.NewPairFromString(ob.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var bids, asks []orderbook.Item
|
||||
for x := range ob.Bids {
|
||||
var price, amount float64
|
||||
@@ -135,7 +129,7 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
ExchangeName: b.Name,
|
||||
})
|
||||
} else {
|
||||
err = b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
err = b.Websocket.Orderbook.Update(&buffer.Update{
|
||||
UpdateTime: ob.Timestamp,
|
||||
Asset: asset.Spot,
|
||||
Bids: bids,
|
||||
@@ -147,26 +141,31 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
case tradeEndPoint:
|
||||
var trade WsTrade
|
||||
err := json.Unmarshal(respRaw, &trade)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := currency.NewPairFromString(trade.Currency)
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
|
||||
p, err := currency.NewPairFromString(trade.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
side := order.Buy
|
||||
if trade.Side == "Ask" {
|
||||
side = order.Sell
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- stream.TradeData{
|
||||
Timestamp: trade.Timestamp,
|
||||
CurrencyPair: p,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
Price: trade.Price,
|
||||
Amount: trade.Volume,
|
||||
Side: order.UnknownSide,
|
||||
Side: side,
|
||||
EventType: order.UnknownType,
|
||||
}
|
||||
case tick:
|
||||
@@ -176,7 +175,10 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(tick.Currency)
|
||||
p, err := currency.NewPairFromString(tick.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: b.Name,
|
||||
@@ -247,12 +249,16 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
p := currency.NewPairFromString(orderData.MarketID)
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
|
||||
p, err := currency.NewPairFromString(orderData.MarketID)
|
||||
if err != nil {
|
||||
return err
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: price,
|
||||
Amount: originalAmount,
|
||||
@@ -263,7 +269,7 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
AssetType: asset.Spot,
|
||||
Date: orderData.Timestamp,
|
||||
Trades: trades,
|
||||
Pair: p,
|
||||
@@ -276,91 +282,80 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
return fmt.Errorf("%v websocket error. Code: %v Message: %v", b.Name, wsErr.Code, wsErr.Message)
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BTCMarkets) generateDefaultSubscriptions() {
|
||||
var channels = []string{tick, tradeEndPoint, wsOB}
|
||||
enabledCurrencies := b.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
func (b *BTCMarkets) generateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var channels = []string{wsOB, tick, tradeEndPoint}
|
||||
enabledCurrencies, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
for i := range channels {
|
||||
for j := range enabledCurrencies {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: channels[i],
|
||||
Currency: enabledCurrencies[j],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
b.Websocket.SubscribeToChannels(subscriptions)
|
||||
|
||||
var authChannels = []string{fundChange, heartbeat, orderChange}
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
for i := range authChannels {
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: authChannels[i],
|
||||
})
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *BTCMarkets) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
unauthChannels := []string{tick, tradeEndPoint, wsOB}
|
||||
authChannels := []string{fundChange, heartbeat, orderChange}
|
||||
switch {
|
||||
case common.StringDataCompare(unauthChannels, channelToSubscribe.Channel):
|
||||
req := WsSubscribe{
|
||||
MarketIDs: []string{b.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()},
|
||||
Channels: []string{channelToSubscribe.Channel},
|
||||
MessageType: subscribe,
|
||||
}
|
||||
err := b.WebsocketConn.SendJSONMessage(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case common.StringDataCompare(authChannels, channelToSubscribe.Channel):
|
||||
message, ok := channelToSubscribe.Params["AuthSub"].(WsAuthSubscribe)
|
||||
if !ok {
|
||||
return errors.New("invalid params data")
|
||||
}
|
||||
tempAuthData := b.generateAuthSubscriptions()
|
||||
message.Channels = append(message.Channels, channelToSubscribe.Channel, heartbeat)
|
||||
message.Key = tempAuthData.Key
|
||||
message.Signature = tempAuthData.Signature
|
||||
message.Timestamp = tempAuthData.Timestamp
|
||||
err := b.WebsocketConn.SendJSONMessage(message)
|
||||
if err != nil {
|
||||
return err
|
||||
func (b *BTCMarkets) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
var authChannels = []string{fundChange, heartbeat, orderChange}
|
||||
|
||||
var payload WsSubscribe
|
||||
payload.MessageType = subscribe
|
||||
|
||||
for i := range channelsToSubscribe {
|
||||
payload.Channels = append(payload.Channels,
|
||||
channelsToSubscribe[i].Channel)
|
||||
|
||||
if channelsToSubscribe[i].Currency.String() != "" {
|
||||
if !common.StringDataCompare(payload.MarketIDs,
|
||||
channelsToSubscribe[i].Currency.String()) {
|
||||
payload.MarketIDs = append(payload.MarketIDs,
|
||||
channelsToSubscribe[i].Currency.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range authChannels {
|
||||
if !common.StringDataCompare(payload.Channels, authChannels[i]) {
|
||||
continue
|
||||
}
|
||||
signTime := strconv.FormatInt(time.Now().UTC().UnixNano()/1000000, 10)
|
||||
strToSign := "/users/self/subscribe" + "\n" + signTime
|
||||
tempSign := crypto.GetHMAC(crypto.HashSHA512,
|
||||
[]byte(strToSign),
|
||||
[]byte(b.API.Credentials.Secret))
|
||||
sign := crypto.Base64Encode(tempSign)
|
||||
payload.Key = b.API.Credentials.Key
|
||||
payload.Signature = sign
|
||||
payload.Timestamp = signTime
|
||||
break
|
||||
}
|
||||
|
||||
err := b.Websocket.Conn.SendJSONMessage(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login logs in allowing private ws events
|
||||
func (b *BTCMarkets) generateAuthSubscriptions() WsAuthSubscribe {
|
||||
var authSubInfo WsAuthSubscribe
|
||||
signTime := strconv.FormatInt(time.Now().UTC().UnixNano()/1000000, 10)
|
||||
strToSign := "/users/self/subscribe" + "\n" + signTime
|
||||
tempSign := crypto.GetHMAC(crypto.HashSHA512,
|
||||
[]byte(strToSign),
|
||||
[]byte(b.API.Credentials.Secret))
|
||||
sign := crypto.Base64Encode(tempSign)
|
||||
authSubInfo.Key = b.API.Credentials.Key
|
||||
authSubInfo.Signature = sign
|
||||
authSubInfo.Timestamp = signTime
|
||||
return authSubInfo
|
||||
}
|
||||
|
||||
// createChannels creates channels that need to be
|
||||
func (b *BTCMarkets) createChannels() {
|
||||
tempChannels := []string{orderChange, fundChange}
|
||||
var channels []wshandler.WebsocketChannelSubscription
|
||||
pairArray := b.GetEnabledPairs(asset.Spot)
|
||||
for y := range tempChannels {
|
||||
for x := range pairArray {
|
||||
var authSub WsAuthSubscribe
|
||||
var channel wshandler.WebsocketChannelSubscription
|
||||
channel.Params = make(map[string]interface{})
|
||||
channel.Channel = tempChannels[y]
|
||||
authSub.MarketIDs = append(authSub.MarketIDs, b.FormatExchangeCurrency(pairArray[x], asset.Spot).String())
|
||||
authSub.MessageType = subscribe
|
||||
channel.Params["AuthSub"] = authSub
|
||||
channels = append(channels, channel)
|
||||
}
|
||||
}
|
||||
b.Websocket.SubscribeToChannels(channels)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"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/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -59,19 +59,11 @@ func (b *BTCMarkets) SetDefaults() {
|
||||
b.API.Endpoints.URLDefault = btcMarketsAPIURL
|
||||
b.API.Endpoints.URL = b.API.Endpoints.URLDefault
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Delimiter: "-",
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Delimiter: "-",
|
||||
Uppercase: true,
|
||||
},
|
||||
requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true}
|
||||
err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
b.Features = exchange.Features{
|
||||
@@ -131,7 +123,7 @@ func (b *BTCMarkets) SetDefaults() {
|
||||
request.WithLimiter(SetRateLimit()))
|
||||
|
||||
b.API.Endpoints.WebsocketURL = btcMarketsWSURL
|
||||
b.Websocket = wshandler.New()
|
||||
b.Websocket = stream.New()
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
@@ -149,41 +141,30 @@ func (b *BTCMarkets) Setup(exch *config.ExchangeConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Websocket.Setup(
|
||||
&wshandler.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: btcMarketsWSURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
err = b.Websocket.Setup(&stream.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: btcMarketsWSURL,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
GenerateSubscriptions: b.generateDefaultSubscriptions,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit,
|
||||
BufferEnabled: true,
|
||||
SortBuffer: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: b.Websocket.GetWebsocketURL(),
|
||||
ProxyURL: b.Websocket.GetProxyAddress(),
|
||||
Verbose: b.Verbose,
|
||||
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Start starts the BTC Markets go routine
|
||||
@@ -205,10 +186,34 @@ func (b *BTCMarkets) Run() {
|
||||
btcMarketsWSURL)
|
||||
b.PrintEnabledPairs()
|
||||
}
|
||||
|
||||
forceUpdate := false
|
||||
delim := b.GetPairFormat(asset.Spot, false).Delimiter
|
||||
if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), delim) ||
|
||||
!common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), delim) {
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s Failed to update enabled currencies Err:%s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return
|
||||
}
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s Failed to update enabled currencies.\n",
|
||||
b.Name)
|
||||
return
|
||||
}
|
||||
|
||||
avail, err := b.GetAvailablePairs(asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s Failed to update enabled currencies.\n",
|
||||
b.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if !common.StringDataContains(pairs.Strings(), format.Delimiter) ||
|
||||
!common.StringDataContains(avail.Strings(), format.Delimiter) {
|
||||
log.Warnln(log.ExchangeSys, "Available pairs for BTC Markets reset due to config upgrade, please enable the pairs you would like again.")
|
||||
forceUpdate = true
|
||||
}
|
||||
@@ -216,10 +221,10 @@ func (b *BTCMarkets) Run() {
|
||||
enabledPairs := currency.Pairs{currency.Pair{
|
||||
Base: currency.BTC.Lower(),
|
||||
Quote: currency.AUD.Lower(),
|
||||
Delimiter: delim,
|
||||
Delimiter: format.Delimiter,
|
||||
},
|
||||
}
|
||||
err := b.UpdatePairs(enabledPairs, asset.Spot, true, true)
|
||||
err = b.UpdatePairs(enabledPairs, asset.Spot, true, true)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s Failed to update enabled currencies.\n",
|
||||
@@ -231,7 +236,7 @@ func (b *BTCMarkets) Run() {
|
||||
return
|
||||
}
|
||||
|
||||
err := b.UpdateTradablePairs(forceUpdate)
|
||||
err = b.UpdateTradablePairs(forceUpdate)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update tradable pairs. Err: %s",
|
||||
@@ -264,28 +269,49 @@ func (b *BTCMarkets) UpdateTradablePairs(forceUpdate bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate)
|
||||
return b.UpdatePairs(p, asset.Spot, false, forceUpdate)
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
allPairs := b.GetEnabledPairs(assetType)
|
||||
tickers, err := b.GetTickers(allPairs.Slice())
|
||||
allPairs, err := b.GetEnabledPairs(assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tickers, err := b.GetTickers(allPairs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(allPairs) != len(tickers) {
|
||||
return nil, errors.New("enabled pairs differ from returned tickers")
|
||||
}
|
||||
|
||||
for x := range tickers {
|
||||
var resp ticker.Price
|
||||
resp.Pair = currency.NewPairFromString(tickers[x].MarketID)
|
||||
resp.Last = tickers[x].LastPrice
|
||||
resp.High = tickers[x].High24h
|
||||
resp.Low = tickers[x].Low24h
|
||||
resp.Bid = tickers[x].BestBID
|
||||
resp.Ask = tickers[x].BestAsk
|
||||
resp.Volume = tickers[x].Volume
|
||||
resp.LastUpdated = time.Now()
|
||||
err = ticker.ProcessTicker(b.Name, &resp, assetType)
|
||||
var newP currency.Pair
|
||||
newP, err = currency.NewPairFromString(tickers[x].MarketID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Pair: newP,
|
||||
Last: tickers[x].LastPrice,
|
||||
High: tickers[x].High24h,
|
||||
Low: tickers[x].Low24h,
|
||||
Bid: tickers[x].BestBID,
|
||||
Ask: tickers[x].BestAsk,
|
||||
Volume: tickers[x].Volume,
|
||||
LastUpdated: time.Now(),
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -313,11 +339,17 @@ func (b *BTCMarkets) FetchOrderbook(p currency.Pair, assetType asset.Item) (*ord
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
orderBook := new(orderbook.Base)
|
||||
tempResp, err := b.GetOrderbook(b.FormatExchangeCurrency(p, assetType).String(), 2)
|
||||
fpair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempResp, err := b.GetOrderbook(fpair.String(), 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBook := new(orderbook.Base)
|
||||
for x := range tempResp.Bids {
|
||||
orderBook.Bids = append(orderBook.Bids, orderbook.Item{
|
||||
Amount: tempResp.Bids[x].Volume,
|
||||
@@ -401,7 +433,12 @@ func (b *BTCMarkets) SubmitOrder(s *order.Submit) (order.SubmitResponse, error)
|
||||
s.Side = order.Bid
|
||||
}
|
||||
|
||||
tempResp, err := b.NewOrder(b.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
fpair, err := b.FormatExchangeCurrency(s.Pair, asset.Spot)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
tempResp, err := b.NewOrder(fpair.String(),
|
||||
s.Price,
|
||||
s.Amount,
|
||||
s.Type.String(),
|
||||
@@ -468,9 +505,15 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
p, err := currency.NewPairFromString(o.MarketID)
|
||||
if err != nil {
|
||||
return order.Detail{}, err
|
||||
}
|
||||
|
||||
resp.Exchange = b.Name
|
||||
resp.ID = orderID
|
||||
resp.Pair = currency.NewPairFromString(o.MarketID)
|
||||
resp.Pair = p
|
||||
resp.Price = o.Price
|
||||
resp.Date = o.CreationTime
|
||||
resp.ExecutedAmount = o.Amount - o.OpenAmount
|
||||
@@ -569,11 +612,6 @@ func (b *BTCMarkets) WithdrawFiatFundsToInternationalBank(withdrawRequest *withd
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *BTCMarkets) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return b.Websocket, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (b *BTCMarkets) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if !b.AllowAuthenticatedRequest() && // Todo check connection status
|
||||
@@ -586,7 +624,10 @@ func (b *BTCMarkets) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, err
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *BTCMarkets) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
if len(req.Pairs) == 0 {
|
||||
allPairs := b.GetEnabledPairs(asset.Spot)
|
||||
allPairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for a := range allPairs {
|
||||
req.Pairs = append(req.Pairs,
|
||||
allPairs[a])
|
||||
@@ -595,7 +636,11 @@ func (b *BTCMarkets) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detai
|
||||
|
||||
var resp []order.Detail
|
||||
for x := range req.Pairs {
|
||||
tempData, err := b.GetOrders(b.FormatExchangeCurrency(req.Pairs[x], asset.Spot).String(), -1, -1, -1, true)
|
||||
fpair, err := b.FormatExchangeCurrency(req.Pairs[x], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tempData, err := b.GetOrders(fpair.String(), -1, -1, -1, true)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
@@ -666,7 +711,12 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai
|
||||
}
|
||||
}
|
||||
for y := range req.Pairs {
|
||||
orders, err := b.GetOrders(b.FormatExchangeCurrency(req.Pairs[y], asset.Spot).String(), -1, -1, -1, false)
|
||||
fpair, err := b.FormatExchangeCurrency(req.Pairs[y], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orders, err := b.GetOrders(fpair.String(), -1, -1, -1, false)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
@@ -697,8 +747,14 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai
|
||||
case orderAccepted:
|
||||
continue
|
||||
}
|
||||
|
||||
p, err := currency.NewPairFromString(tempData.Orders[c].MarketID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempResp.Exchange = b.Name
|
||||
tempResp.Pair = currency.NewPairFromString(tempData.Orders[c].MarketID)
|
||||
tempResp.Pair = p
|
||||
tempResp.Side = order.Bid
|
||||
if tempData.Orders[c].Side == ask {
|
||||
tempResp.Side = order.Ask
|
||||
@@ -713,28 +769,6 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *BTCMarkets) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *BTCMarkets) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *BTCMarkets) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *BTCMarkets) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (b *BTCMarkets) ValidateCredentials() error {
|
||||
@@ -776,7 +810,12 @@ func (b *BTCMarkets) GetHistoricCandles(pair currency.Pair, a asset.Item, start,
|
||||
return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits)
|
||||
}
|
||||
|
||||
candles, err := b.GetMarketCandles(b.FormatExchangeCurrency(pair, a).String(),
|
||||
formattedPair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
candles, err := b.GetMarketCandles(formattedPair.String(),
|
||||
b.FormatExchangeKlineInterval(interval),
|
||||
start,
|
||||
end,
|
||||
@@ -789,7 +828,7 @@ func (b *BTCMarkets) GetHistoricCandles(pair currency.Pair, a asset.Item, start,
|
||||
}
|
||||
ret := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: b.FormatExchangeCurrency(pair, a),
|
||||
Pair: formattedPair,
|
||||
Asset: asset.Spot,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
@@ -15,19 +15,18 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
// BTSE is the overarching type across this package
|
||||
type BTSE struct {
|
||||
exchange.Base
|
||||
WebsocketConn *wshandler.WebsocketConnection
|
||||
}
|
||||
|
||||
const (
|
||||
btseAPIURL = "https://api.btse.com"
|
||||
btseAPIPath = "/spot/v2/"
|
||||
btseAPIURL = "https://api.btse.com"
|
||||
btseSPOTAPIPath = "/spot/v2/"
|
||||
btseFuturesAPIPath = "/futures/api/v2.1/"
|
||||
|
||||
// Public endpoints
|
||||
btseMarketOverview = "market_summary"
|
||||
@@ -50,34 +49,40 @@ const (
|
||||
// GetMarketsSummary stores market summary data
|
||||
func (b *BTSE) GetMarketsSummary() (*HighLevelMarketData, error) {
|
||||
var m HighLevelMarketData
|
||||
return &m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m)
|
||||
return &m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m, true)
|
||||
}
|
||||
|
||||
// GetMarkets returns a list of markets available on BTSE
|
||||
func (b *BTSE) GetMarkets() ([]Market, error) {
|
||||
var m []Market
|
||||
return m, b.SendHTTPRequest(http.MethodGet, btseMarkets, &m)
|
||||
// GetSpotMarkets returns a list of spot markets available on BTSE
|
||||
func (b *BTSE) GetSpotMarkets() ([]SpotMarket, error) {
|
||||
var m []SpotMarket
|
||||
return m, b.SendHTTPRequest(http.MethodGet, btseMarkets, &m, true)
|
||||
}
|
||||
|
||||
// GetFuturesMarkets returns a list of futures markets available on BTSE
|
||||
func (b *BTSE) GetFuturesMarkets() ([]FuturesMarket, error) {
|
||||
var m []FuturesMarket
|
||||
return m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m, false)
|
||||
}
|
||||
|
||||
// FetchOrderBook gets orderbook data for a given pair
|
||||
func (b *BTSE) FetchOrderBook(symbol string) (*Orderbook, error) {
|
||||
var o Orderbook
|
||||
endpoint := fmt.Sprintf("%s/%s", btseOrderbook, symbol)
|
||||
return &o, b.SendHTTPRequest(http.MethodGet, endpoint, &o)
|
||||
return &o, b.SendHTTPRequest(http.MethodGet, endpoint, &o, true)
|
||||
}
|
||||
|
||||
// GetTrades returns a list of trades for the specified symbol
|
||||
func (b *BTSE) GetTrades(symbol string) ([]Trade, error) {
|
||||
var t []Trade
|
||||
endpoint := fmt.Sprintf("%s/%s", btseTrades, symbol)
|
||||
return t, b.SendHTTPRequest(http.MethodGet, endpoint, &t)
|
||||
return t, b.SendHTTPRequest(http.MethodGet, endpoint, &t, true)
|
||||
}
|
||||
|
||||
// GetTicker returns the ticker for a specified symbol
|
||||
func (b *BTSE) GetTicker(symbol string) (*Ticker, error) {
|
||||
var t Ticker
|
||||
endpoint := fmt.Sprintf("%s/%s", btseTicker, symbol)
|
||||
err := b.SendHTTPRequest(http.MethodGet, endpoint, &t)
|
||||
err := b.SendHTTPRequest(http.MethodGet, endpoint, &t, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -88,19 +93,19 @@ func (b *BTSE) GetTicker(symbol string) (*Ticker, error) {
|
||||
func (b *BTSE) GetMarketStatistics(symbol string) (*MarketStatistics, error) {
|
||||
var m MarketStatistics
|
||||
endpoint := fmt.Sprintf("%s/%s", btseStats, symbol)
|
||||
return &m, b.SendHTTPRequest(http.MethodGet, endpoint, &m)
|
||||
return &m, b.SendHTTPRequest(http.MethodGet, endpoint, &m, true)
|
||||
}
|
||||
|
||||
// GetServerTime returns the exchanges server time
|
||||
func (b *BTSE) GetServerTime() (*ServerTime, error) {
|
||||
var s ServerTime
|
||||
return &s, b.SendHTTPRequest(http.MethodGet, btseTime, &s)
|
||||
return &s, b.SendHTTPRequest(http.MethodGet, btseTime, &s, true)
|
||||
}
|
||||
|
||||
// GetAccountBalance returns the users account balance
|
||||
func (b *BTSE) GetAccountBalance() ([]CurrencyBalance, error) {
|
||||
var a []CurrencyBalance
|
||||
return a, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseAccount, nil, &a)
|
||||
return a, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseAccount, nil, &a, true)
|
||||
}
|
||||
|
||||
// CreateOrder creates an order
|
||||
@@ -129,7 +134,7 @@ func (b *BTSE) CreateOrder(amount, price float64, side, orderType, symbol, timeI
|
||||
}
|
||||
|
||||
var r orderResp
|
||||
return &r.ID, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseOrder, req, &r)
|
||||
return &r.ID, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseOrder, req, &r, true)
|
||||
}
|
||||
|
||||
// GetOrders returns all pending orders
|
||||
@@ -139,7 +144,7 @@ func (b *BTSE) GetOrders(symbol string) ([]OpenOrder, error) {
|
||||
req["symbol"] = symbol
|
||||
}
|
||||
var o []OpenOrder
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, req, &o)
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, req, &o, true)
|
||||
}
|
||||
|
||||
// CancelExistingOrder cancels an order
|
||||
@@ -148,7 +153,7 @@ func (b *BTSE) CancelExistingOrder(orderID, symbol string) (*CancelOrder, error)
|
||||
req := make(map[string]interface{})
|
||||
req["order_id"] = orderID
|
||||
req["symbol"] = symbol
|
||||
return &c, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseDeleteOrder, req, &c)
|
||||
return &c, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseDeleteOrder, req, &c, true)
|
||||
}
|
||||
|
||||
// GetFills gets all filled orders
|
||||
@@ -184,14 +189,18 @@ func (b *BTSE) GetFills(orderID, symbol, before, after, limit, username string)
|
||||
}
|
||||
|
||||
var o []FilledOrder
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseFills, req, &o)
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseFills, req, &o, true)
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an HTTP request to the desired endpoint
|
||||
func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}) error {
|
||||
func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}, spotEndpoint bool) error {
|
||||
p := btseSPOTAPIPath
|
||||
if !spotEndpoint {
|
||||
p = btseFuturesAPIPath
|
||||
}
|
||||
return b.SendPayload(context.Background(), &request.Item{
|
||||
Method: method,
|
||||
Path: b.API.Endpoints.URL + btseAPIPath + endpoint,
|
||||
Path: b.API.Endpoints.URL + p + endpoint,
|
||||
Result: result,
|
||||
Verbose: b.Verbose,
|
||||
HTTPDebugging: b.HTTPDebugging,
|
||||
@@ -200,12 +209,18 @@ func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}) erro
|
||||
}
|
||||
|
||||
// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the desired endpoint
|
||||
func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[string]interface{}, result interface{}) error {
|
||||
func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[string]interface{}, result interface{}, spotEndpoint bool) error {
|
||||
if !b.AllowAuthenticatedRequest() {
|
||||
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet,
|
||||
b.Name)
|
||||
}
|
||||
path := btseAPIPath + endpoint
|
||||
|
||||
p := btseSPOTAPIPath
|
||||
if !spotEndpoint {
|
||||
p = btseFuturesAPIPath
|
||||
}
|
||||
|
||||
path := p + endpoint
|
||||
headers := make(map[string]string)
|
||||
headers["btse-api"] = b.API.Credentials.Key
|
||||
nonce := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
|
||||
@@ -39,13 +39,11 @@ func TestMain(m *testing.M) {
|
||||
btseConfig.API.AuthenticatedSupport = true
|
||||
btseConfig.API.Credentials.Key = apiKey
|
||||
btseConfig.API.Credentials.Secret = apiSecret
|
||||
|
||||
b.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = b.Setup(btseConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -61,9 +59,17 @@ func TestGetMarketsSummary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarkets(t *testing.T) {
|
||||
func TestGetSpotMarkets(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetMarkets()
|
||||
_, err := b.GetSpotMarkets()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFuturesMarkets(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetFuturesMarkets()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -411,3 +417,16 @@ func TestStatusToStandardStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchTradablePairs(t *testing.T) {
|
||||
assets := b.GetAssetTypes()
|
||||
for i := range assets {
|
||||
data, err := b.FetchTradablePairs(assets[i])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
t.Fatal("data cannot be zero")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ type OverviewData struct {
|
||||
// HighLevelMarketData stores market overview data
|
||||
type HighLevelMarketData map[string]OverviewData
|
||||
|
||||
// Market stores market data
|
||||
type Market struct {
|
||||
// SpotMarket stores market data
|
||||
type SpotMarket struct {
|
||||
Symbol string `json:"symbol"`
|
||||
ID string `json:"id"`
|
||||
BaseCurrency string `json:"base_currency"`
|
||||
@@ -35,6 +35,41 @@ type Market struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// FuturesMarket stores market data
|
||||
type FuturesMarket struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Last float64 `json:"last"`
|
||||
LowestAsk float64 `json:"lowestAsk"`
|
||||
HighestBid float64 `json:"highestBid"`
|
||||
OpenInterest float64 `json:"openInterest"`
|
||||
OpenInterestUSD float64 `json:"openInterestUSD"`
|
||||
PercentageChange float64 `json:"percentageChange"`
|
||||
Volume float64 `json:"volume"`
|
||||
High24Hr float64 `json:"high24Hr"`
|
||||
Low24Hr float64 `json:"low24Hr"`
|
||||
Base string `json:"base"`
|
||||
Quote string `json:"quote"`
|
||||
ContractStart int64 `json:"contractStart"`
|
||||
ContractEnd int64 `json:"contractEnd"`
|
||||
Active bool `json:"active"`
|
||||
TimeBasedContract bool `json:"timeBasedContract"`
|
||||
OpenTime int64 `json:"openTime"`
|
||||
CloseTime int64 `json:"closeTime"`
|
||||
StartMatching int64 `json:"startMatching"`
|
||||
InactiveTime int64 `json:"inactiveTime"`
|
||||
FundingRate float64 `json:"fundingRate"`
|
||||
ContractSize float64 `json:"contractSize"`
|
||||
MaxPosition int64 `json:"maxPosition"`
|
||||
MinValidPrice float64 `json:"minValidPrice"`
|
||||
MinPriceIncrement float64 `json:"minPriceIncrement"`
|
||||
MinOrderSize int32 `json:"minOrderSize"`
|
||||
MaxOrderSize int32 `json:"maxOrderSize"`
|
||||
MinRiskLimit int32 `json:"minRiskLimit"`
|
||||
MaxRiskLimit int32 `json:"maxRiskLimit"`
|
||||
MinSizeIncrement float64 `json:"minSizeIncrement"`
|
||||
AvailableSettlement []string `json:"availableSettlement"`
|
||||
}
|
||||
|
||||
// Trade stores trade data
|
||||
type Trade struct {
|
||||
SerialID string `json:"serial_id"`
|
||||
|
||||
@@ -16,25 +16,25 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
)
|
||||
|
||||
const (
|
||||
btseWebsocket = "wss://ws.btse.com/spotWS"
|
||||
btseWebsocketTimer = 57 * time.Second
|
||||
btseWebsocketTimer = time.Second * 57
|
||||
)
|
||||
|
||||
// WsConnect connects the websocket client
|
||||
func (b *BTSE) WsConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
return errors.New(stream.WebsocketNotEnabled)
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := b.WebsocketConn.Dial(&dialer, http.Header{})
|
||||
err := b.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{
|
||||
b.Websocket.Conn.SetupPingHandler(stream.PingHandler{
|
||||
MessageType: websocket.PingMessage,
|
||||
Delay: btseWebsocketTimer,
|
||||
})
|
||||
@@ -48,17 +48,19 @@ func (b *BTSE) WsConnect() error {
|
||||
}
|
||||
}
|
||||
|
||||
b.GenerateDefaultSubscriptions()
|
||||
return nil
|
||||
subs, err := b.GenerateDefaultSubscriptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Websocket.SubscribeToChannels(subs)
|
||||
}
|
||||
|
||||
// WsAuthenticate Send an authentication message to receive auth data
|
||||
func (b *BTSE) WsAuthenticate() error {
|
||||
nonce := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
path := "/spotWS" + nonce
|
||||
hmac := crypto.GetHMAC(
|
||||
crypto.HashSHA512_384,
|
||||
[]byte((path + nonce)),
|
||||
hmac := crypto.GetHMAC(crypto.HashSHA512_384,
|
||||
[]byte((path)),
|
||||
[]byte(b.API.Credentials.Secret),
|
||||
)
|
||||
sign := crypto.HexEncodeToString(hmac)
|
||||
@@ -66,7 +68,7 @@ func (b *BTSE) WsAuthenticate() error {
|
||||
Operation: "authKeyExpires",
|
||||
Arguments: []string{b.API.Credentials.Key, nonce, sign},
|
||||
}
|
||||
return b.WebsocketConn.SendJSONMessage(req)
|
||||
return b.Websocket.Conn.SendJSONMessage(req)
|
||||
}
|
||||
|
||||
func stringToOrderStatus(status string) (order.Status, error) {
|
||||
@@ -93,27 +95,16 @@ func stringToOrderStatus(status string) (order.Status, error) {
|
||||
// wsReadData receives and passes on websocket messages for processing
|
||||
func (b *BTSE) wsReadData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
defer b.Websocket.Wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
resp := b.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
|
||||
default:
|
||||
resp, err := b.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
b.Websocket.ReadMessageErrors <- err
|
||||
return
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
err = b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
err := b.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +114,13 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
|
||||
var result Result
|
||||
err := json.Unmarshal(respRaw, &result)
|
||||
if err != nil {
|
||||
if strings.Contains(string(respRaw), "UNLOGIN_USER connect success") ||
|
||||
strings.Contains(string(respRaw), "authenticated successfully") {
|
||||
return nil
|
||||
} else if strings.Contains(string(respRaw), "AUTHENTICATE ERROR") {
|
||||
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
return errors.New("authentication failure")
|
||||
}
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
@@ -160,12 +158,19 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
p := currency.NewPairFromString(notification.Data[i].Symbol)
|
||||
|
||||
var p currency.Pair
|
||||
p, err = currency.NewPairFromString(notification.Data[i].Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: notification.Data[i].Price,
|
||||
Amount: notification.Data[i].Size,
|
||||
@@ -192,13 +197,21 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
|
||||
if tradeHistory.Data[x].Gain == -1 {
|
||||
side = order.Sell
|
||||
}
|
||||
p := currency.NewPairFromString(strings.Replace(tradeHistory.Topic, "tradeHistory:", "", 1))
|
||||
|
||||
var p currency.Pair
|
||||
p, err = currency.NewPairFromString(strings.Replace(tradeHistory.Topic,
|
||||
"tradeHistory:",
|
||||
"",
|
||||
1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
b.Websocket.DataHandler <- stream.TradeData{
|
||||
Timestamp: time.Unix(0, tradeHistory.Data[x].TransactionTime*int64(time.Millisecond)),
|
||||
CurrencyPair: p,
|
||||
AssetType: a,
|
||||
@@ -248,7 +261,10 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
p := currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")])
|
||||
p, err := currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
@@ -261,50 +277,65 @@ func (b *BTSE) wsHandleData(respRaw []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
|
||||
Asset: a,
|
||||
Exchange: b.Name}
|
||||
default:
|
||||
b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)}
|
||||
b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *BTSE) GenerateDefaultSubscriptions() {
|
||||
func (b *BTSE) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
||||
var channels = []string{"orderBookApi:%s_0", "tradeHistory:%s"}
|
||||
pairs := b.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
pairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subscriptions []stream.ChannelSubscription
|
||||
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: "notificationApi",
|
||||
})
|
||||
}
|
||||
for i := range channels {
|
||||
for j := range pairs {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
||||
Channel: fmt.Sprintf(channels[i], pairs[j]),
|
||||
Currency: pairs[j],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
b.Websocket.SubscribeToChannels(subscriptions)
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *BTSE) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
func (b *BTSE) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
||||
var sub wsSub
|
||||
sub.Operation = "subscribe"
|
||||
sub.Arguments = []string{channelToSubscribe.Channel}
|
||||
|
||||
return b.WebsocketConn.SendJSONMessage(sub)
|
||||
for i := range channelsToSubscribe {
|
||||
sub.Arguments = append(sub.Arguments, channelsToSubscribe[i].Channel)
|
||||
}
|
||||
err := b.Websocket.Conn.SendJSONMessage(sub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (b *BTSE) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
func (b *BTSE) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
||||
var unSub wsSub
|
||||
unSub.Operation = "unsubscribe"
|
||||
unSub.Arguments = []string{channelToSubscribe.Channel}
|
||||
return b.WebsocketConn.SendJSONMessage(unSub)
|
||||
for i := range channelsToUnsubscribe {
|
||||
unSub.Arguments = append(unSub.Arguments,
|
||||
channelsToUnsubscribe[i].Channel)
|
||||
}
|
||||
err := b.Websocket.Conn.SendJSONMessage(unSub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"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/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
@@ -56,11 +56,7 @@ func (b *BTSE) SetDefaults() {
|
||||
b.API.CredentialsValidator.RequiresKey = true
|
||||
b.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
b.CurrencyPairs = currency.PairsManager{
|
||||
AssetTypes: asset.Items{
|
||||
asset.Spot,
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
fmt1 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
@@ -70,6 +66,23 @@ func (b *BTSE) SetDefaults() {
|
||||
Delimiter: "-",
|
||||
},
|
||||
}
|
||||
err := b.StoreAssetPairFormat(asset.Spot, fmt1)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
fmt2 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
},
|
||||
}
|
||||
err = b.StoreAssetPairFormat(asset.Futures, fmt2)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
b.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
@@ -112,7 +125,7 @@ func (b *BTSE) SetDefaults() {
|
||||
|
||||
b.API.Endpoints.URLDefault = btseAPIURL
|
||||
b.API.Endpoints.URL = b.API.Endpoints.URLDefault
|
||||
b.Websocket = wshandler.New()
|
||||
b.Websocket = stream.New()
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
@@ -130,41 +143,29 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Websocket.Setup(
|
||||
&wshandler.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: btseWebsocket,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
})
|
||||
err = b.Websocket.Setup(&stream.WebsocketSetup{
|
||||
Enabled: exch.Features.Enabled.Websocket,
|
||||
Verbose: exch.Verbose,
|
||||
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
|
||||
WebsocketTimeout: exch.WebsocketTrafficTimeout,
|
||||
DefaultURL: btseWebsocket,
|
||||
ExchangeName: exch.Name,
|
||||
RunningURL: exch.API.Endpoints.WebsocketURL,
|
||||
Connector: b.WsConnect,
|
||||
Subscriber: b.Subscribe,
|
||||
UnSubscriber: b.Unsubscribe,
|
||||
GenerateSubscriptions: b.GenerateDefaultSubscriptions,
|
||||
Features: &b.Features.Supports.WebsocketCapabilities,
|
||||
OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: b.Websocket.GetWebsocketURL(),
|
||||
ProxyURL: b.Websocket.GetProxyAddress(),
|
||||
Verbose: b.Verbose,
|
||||
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Start starts the BTSE go routine
|
||||
@@ -194,61 +195,96 @@ func (b *BTSE) Run() {
|
||||
}
|
||||
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (b *BTSE) FetchTradablePairs(asset asset.Item) ([]string, error) {
|
||||
m, err := b.GetMarkets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (b *BTSE) FetchTradablePairs(a asset.Item) ([]string, error) {
|
||||
var currencies []string
|
||||
if a == asset.Spot {
|
||||
m, err := b.GetSpotMarkets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for x := range m {
|
||||
if m[x].Status != "active" {
|
||||
continue
|
||||
}
|
||||
currencies = append(currencies, m[x].Symbol)
|
||||
}
|
||||
} else if a == asset.Futures {
|
||||
m, err := b.GetFuturesMarkets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for x := range m {
|
||||
if !m[x].Active {
|
||||
continue
|
||||
}
|
||||
currencies = append(currencies, m[x].Symbol)
|
||||
}
|
||||
}
|
||||
|
||||
var currencies []string
|
||||
for x := range m {
|
||||
if m[x].Status != "active" {
|
||||
continue
|
||||
}
|
||||
currencies = append(currencies, m[x].Symbol)
|
||||
}
|
||||
return currencies, nil
|
||||
}
|
||||
|
||||
// UpdateTradablePairs updates the exchanges available pairs and stores
|
||||
// them in the exchanges config
|
||||
func (b *BTSE) UpdateTradablePairs(forceUpdate bool) error {
|
||||
pairs, err := b.FetchTradablePairs(asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a := b.GetAssetTypes()
|
||||
for i := range a {
|
||||
pairs, err := b.FetchTradablePairs(a[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate)
|
||||
p, err := currency.NewPairsFromStrings(pairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.UpdatePairs(p, a[i], false, forceUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *BTSE) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerPrice := new(ticker.Price)
|
||||
|
||||
t, err := b.GetTicker(b.FormatExchangeCurrency(p,
|
||||
assetType).String())
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
if assetType == asset.Futures {
|
||||
// Futures REST implementation needs to be done before this can be
|
||||
// removed
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
s, err := b.GetMarketStatistics(b.FormatExchangeCurrency(p,
|
||||
assetType).String())
|
||||
fpair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tickerPrice.Pair = p
|
||||
tickerPrice.Ask = t.Ask
|
||||
tickerPrice.Bid = t.Bid
|
||||
tickerPrice.Low = s.Low
|
||||
tickerPrice.Last = t.Price
|
||||
tickerPrice.Volume = s.Volume
|
||||
tickerPrice.High = s.High
|
||||
tickerPrice.LastUpdated = s.Time
|
||||
|
||||
err = ticker.ProcessTicker(b.Name, tickerPrice, assetType)
|
||||
t, err := b.GetTicker(fpair.String())
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := b.GetMarketStatistics(fpair.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Pair: p,
|
||||
Ask: t.Ask,
|
||||
Bid: t.Bid,
|
||||
Low: s.Low,
|
||||
Last: t.Price,
|
||||
Volume: s.Volume,
|
||||
High: s.High,
|
||||
LastUpdated: s.Time,
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ticker.GetTicker(b.Name, p, assetType)
|
||||
}
|
||||
@@ -273,11 +309,22 @@ func (b *BTSE) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbook
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
orderBook := new(orderbook.Base)
|
||||
a, err := b.FetchOrderBook(b.FormatExchangeCurrency(p, assetType).String())
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
if assetType == asset.Futures {
|
||||
// Futures REST implementation needs to be done before this can be
|
||||
// removed
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
fpair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, err := b.FetchOrderBook(fpair.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderBook := new(orderbook.Base)
|
||||
for x := range a.BuyQuote {
|
||||
orderBook.Bids = append(orderBook.Bids, orderbook.Item{
|
||||
Price: a.BuyQuote[x].Price,
|
||||
@@ -360,11 +407,16 @@ func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
fpair, err := b.FormatExchangeCurrency(s.Pair, s.AssetType)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
r, err := b.CreateOrder(s.Amount,
|
||||
s.Price,
|
||||
s.Side.String(),
|
||||
s.Type.String(),
|
||||
b.FormatExchangeCurrency(s.Pair, asset.Spot).String(),
|
||||
fpair.String(),
|
||||
goodTillCancel,
|
||||
s.ClientID)
|
||||
if err != nil {
|
||||
@@ -389,9 +441,13 @@ func (b *BTSE) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *BTSE) CancelOrder(order *order.Cancel) error {
|
||||
r, err := b.CancelExistingOrder(order.ID,
|
||||
b.FormatExchangeCurrency(order.Pair,
|
||||
asset.Spot).String())
|
||||
fpair, err := b.FormatExchangeCurrency(order.Pair,
|
||||
order.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := b.CancelExistingOrder(order.ID, fpair.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -411,19 +467,28 @@ func (b *BTSE) CancelOrder(order *order.Cancel) error {
|
||||
// If not specified, all orders of all markets will be cancelled
|
||||
func (b *BTSE) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) {
|
||||
var resp order.CancelAllResponse
|
||||
markets, err := b.GetMarkets()
|
||||
markets, err := b.GetSpotMarkets()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(orderCancellation.AssetType, false)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp.Status = make(map[string]string)
|
||||
for x := range markets {
|
||||
strPair := b.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
orderCancellation.AssetType).String()
|
||||
fair, err := b.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
orderCancellation.AssetType)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
checkPair := currency.NewPairWithDelimiter(markets[x].BaseCurrency,
|
||||
markets[x].QuoteCurrency,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter).String()
|
||||
if strPair != "" && strPair != checkPair {
|
||||
format.Delimiter).String()
|
||||
if fair.String() != checkPair {
|
||||
continue
|
||||
} else {
|
||||
orders, err := b.GetOrders(checkPair)
|
||||
@@ -455,6 +520,11 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
return od, errors.New("no orders found")
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return order.Detail{}, err
|
||||
}
|
||||
|
||||
for i := range o {
|
||||
if o[i].ID != orderID {
|
||||
continue
|
||||
@@ -465,8 +535,14 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
side = order.Sell
|
||||
}
|
||||
|
||||
od.Pair = currency.NewPairDelimiter(o[i].Symbol,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
od.Pair, err = currency.NewPairDelimiter(o[i].Symbol,
|
||||
format.Delimiter)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s GetOrderInfo unable to parse currency pair: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
od.Exchange = b.Name
|
||||
od.Amount = o[i].Amount
|
||||
od.ID = o[i].ID
|
||||
@@ -530,11 +606,6 @@ func (b *BTSE) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.Re
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (b *BTSE) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return b.Websocket, nil
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
resp, err := b.GetOrders("")
|
||||
@@ -542,6 +613,11 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var orders []order.Detail
|
||||
for i := range resp {
|
||||
var side = order.Buy
|
||||
@@ -557,9 +633,17 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err
|
||||
err)
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(resp[i].Symbol,
|
||||
format.Delimiter)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s GetActiveOrders unable to parse currency pair: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
openOrder := order.Detail{
|
||||
Pair: currency.NewPairDelimiter(resp[i].Symbol,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
Pair: p,
|
||||
Exchange: b.Name,
|
||||
Amount: resp[i].Amount,
|
||||
ID: resp[i].ID,
|
||||
@@ -621,30 +705,6 @@ func (b *BTSE) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
return b.GetFee(feeBuilder)
|
||||
}
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (b *BTSE) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
b.Websocket.SubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (b *BTSE) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
b.Websocket.RemoveSubscribedChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (b *BTSE) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return b.Websocket.GetSubscriptions(), nil
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (b *BTSE) AuthenticateWebsocket() error {
|
||||
return common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (b *BTSE) ValidateCredentials() error {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user