From 0fd433e86557b088e91470dd6e4172c275fb2a9c Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 22 Nov 2023 10:20:09 +1100 Subject: [PATCH] exchanges/bittrex,kucoin: Remove exchange implementation and fix minor test issue (#1403) * exchanges/Bittrex: Remove exchange implementation * Kucoin: Fix TestProcessMarketSnapshot after pair removal update * Kucoin: Fix race due to duplicate setupWS call Unleash your inner Max Verstappen * Kucoin: Actually test spot/margin market snapshot replication --- README.md | 1 - backtester/report/report_test.go | 4 +- cmd/apichecker/testupdates.json | 11 - cmd/apichecker/updates.json | 11 - .../exchanges_templates/bittrex.tmpl | 103 -- .../exchanges_trade_readme.tmpl | 1 - .../root_templates/root_readme.tmpl | 1 - .../exchange_wrapper_standards_test.go | 1 - config_example.json | 78 -- docs/ADD_NEW_EXCHANGE.md | 2 - docs/MULTICHAIN_TRANSFER_SUPPORT.md | 1 - docs/OHLCV.md | 1 - engine/helpers.go | 3 - exchanges/binance/binance.go | 2 +- exchanges/bitmex/bitmex.go | 2 +- exchanges/bittrex/README.md | 137 -- exchanges/bittrex/bittrex.go | 488 -------- exchanges/bittrex/bittrex_test.go | 747 ----------- exchanges/bittrex/bittrex_types.go | 305 ----- exchanges/bittrex/bittrex_websocket.go | 624 ---------- exchanges/bittrex/bittrex_wrapper.go | 1108 ----------------- exchanges/bittrex/bittrex_ws_orderbook.go | 437 ------- exchanges/kucoin/kucoin_test.go | 6 +- .../kucoin/testdata/wsMarketSnapshot.json | 2 +- exchanges/orderbook/orderbook_types.go | 4 +- exchanges/support.go | 1 - exchanges/trade/README.md | 1 - testdata/configtest.json | 80 +- testdata/exchangelist.csv | 1 - 29 files changed, 11 insertions(+), 4152 deletions(-) delete mode 100644 cmd/documentation/exchanges_templates/bittrex.tmpl delete mode 100644 exchanges/bittrex/README.md delete mode 100644 exchanges/bittrex/bittrex.go delete mode 100644 exchanges/bittrex/bittrex_test.go delete mode 100644 exchanges/bittrex/bittrex_types.go delete mode 100644 exchanges/bittrex/bittrex_websocket.go delete mode 100644 exchanges/bittrex/bittrex_wrapper.go delete mode 100644 exchanges/bittrex/bittrex_ws_orderbook.go diff --git a/README.md b/README.md index 31e90575..68c8bc5c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | Bithumb | Yes | Yes | NA | | BitMEX | Yes | Yes | NA | | Bitstamp | Yes | Yes | No | -| Bittrex | Yes | Yes | NA | | BTCMarkets | Yes | Yes | NA | | BTSE | Yes | Yes | NA | | Bybit | Yes | Yes | NA | diff --git a/backtester/report/report_test.go b/backtester/report/report_test.go index 68da9d40..4b258110 100644 --- a/backtester/report/report_test.go +++ b/backtester/report/report_test.go @@ -135,11 +135,11 @@ func TestGenerateReport(t *testing.T) { }, }, { - Exchange: "Bittrex", + Exchange: "Bitstamp", Asset: a, Pair: currency.NewPair(currency.BTC, currency.USD), Interval: gctkline.OneDay, - Watermark: "BITTREX - SPOT - BTC-USD - 1d", + Watermark: "BITSTAMP - SPOT - BTC-USD - 1d", Candles: []DetailedCandle{ { UnixMilli: time.Date(2020, 12, 12, 0, 0, 0, 0, time.UTC).UnixMilli(), diff --git a/cmd/apichecker/testupdates.json b/cmd/apichecker/testupdates.json index c15915b3..75a83e12 100644 --- a/cmd/apichecker/testupdates.json +++ b/cmd/apichecker/testupdates.json @@ -72,17 +72,6 @@ }, "Disabled": false }, - { - "Name": "Bittrex", - "CheckType": "GitHub Sha Check", - "Data": { - "GitHubData": { - "Repo": "Bittrex/bittrex.github.io", - "Sha": "fc1ea9c10c48aa82c4dc2c6be74887ef61b5b31b" - } - }, - "Disabled": false - }, { "Name": "Coinut", "CheckType": "GitHub Sha Check", diff --git a/cmd/apichecker/updates.json b/cmd/apichecker/updates.json index 49ada374..fefb4327 100644 --- a/cmd/apichecker/updates.json +++ b/cmd/apichecker/updates.json @@ -72,17 +72,6 @@ }, "Disabled": false }, - { - "Name": "Bittrex", - "CheckType": "GitHub Sha Check", - "Data": { - "GitHubData": { - "Repo": "Bittrex/bittrex.github.io", - "Sha": "fc1ea9c10c48aa82c4dc2c6be74887ef61b5b31b" - } - }, - "Disabled": false - }, { "Name": "Coinut", "CheckType": "GitHub Sha Check", diff --git a/cmd/documentation/exchanges_templates/bittrex.tmpl b/cmd/documentation/exchanges_templates/bittrex.tmpl deleted file mode 100644 index 968448f8..00000000 --- a/cmd/documentation/exchanges_templates/bittrex.tmpl +++ /dev/null @@ -1,103 +0,0 @@ -{{define "exchanges bittrex" -}} -{{template "header" .}} -## Bittrex Exchange - -### Current Features - -+ REST Support - -### Notes - -- Bittrex used to have reversed market names: btc-ltc. The v3 API changed this to the more widely accepted format with first the base pair and then the quote pair: ltc-btc. -- Asset names and market names are not case sensitive. - -### How to enable - -+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example) - -+ Individual package example below: - -```go - // Exchanges will be abstracted out in further updates and examples will be - // supplied then -``` - -### How to do REST public/private calls - -+ If enabled via "configuration".json file the exchange will be added to the -IBotExchange array in the ```go var bot Bot``` and you will only be able to use -the wrapper interface functions for accessing exchange data. View routines.go -for an example of integration usage with GoCryptoTrader. Rudimentary example -below: - -main.go -```go -var b exchange.IBotExchange - -for i := range bot.Exchanges { - if bot.Exchanges[i].GetName() == "Bittrex" { - b = bot.Exchanges[i] - } -} - -// Public calls - wrapper functions - -// Fetches current ticker information -tick, err := b.FetchTicker() -if err != nil { - // Handle error -} - -// Fetches current orderbook information -ob, err := b.FetchOrderbook() -if err != nil { - // Handle error -} - -// Private calls - wrapper functions - make sure your APIKEY and APISECRET are -// set and AuthenticatedAPISupport is set to true - -// Fetches current account information -accountInfo, err := b.GetAccountInfo() -if err != nil { - // Handle error -} -``` - -+ If enabled via individually importing package, rudimentary example below: - -```go -// Public calls - -// Fetches current ticker information -ticker, err := b.GetTicker() -if err != nil { - // Handle error -} - -// Fetches current orderbook information -ob, err := b.GetOrderBook() -if err != nil { - // Handle error -} - -// Private calls - make sure your APIKEY and APISECRET are set and -// AuthenticatedAPISupport is set to true - -// GetUserInfo returns account info -accountInfo, err := b.GetUserInfo(...) -if err != nil { - // Handle error -} - -// Submits an order and the exchange and returns its tradeID -tradeID, err := b.Trade(...) -if err != nil { - // Handle error -} -``` - -### Please click GoDocs chevron above to view current GoDoc information for this package -{{template "contributions"}} -{{template "donations" .}} -{{end}} diff --git a/cmd/documentation/exchanges_templates/exchanges_trade_readme.tmpl b/cmd/documentation/exchanges_templates/exchanges_trade_readme.tmpl index 51b79314..cc0d02f2 100644 --- a/cmd/documentation/exchanges_templates/exchanges_trade_readme.tmpl +++ b/cmd/documentation/exchanges_templates/exchanges_trade_readme.tmpl @@ -49,7 +49,6 @@ _b in this context is an `IBotExchange` implemented struct_ | Bithumb | Yes | Yes | No | | BitMEX | Yes | Yes | Yes | | Bitstamp | Yes | Yes | No | -| Bittrex | Yes | Yes | No | | BTCMarkets | Yes | Yes | No | | BTSE | Yes | Yes | No | | Bybit | Yes | Yes | Yes | diff --git a/cmd/documentation/root_templates/root_readme.tmpl b/cmd/documentation/root_templates/root_readme.tmpl index d1346219..063bec6b 100644 --- a/cmd/documentation/root_templates/root_readme.tmpl +++ b/cmd/documentation/root_templates/root_readme.tmpl @@ -27,7 +27,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | Bithumb | Yes | Yes | NA | | BitMEX | Yes | Yes | NA | | Bitstamp | Yes | Yes | No | -| Bittrex | Yes | Yes | NA | | BTCMarkets | Yes | Yes | NA | | BTSE | Yes | Yes | NA | | Bybit | Yes | Yes | NA | diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index d60abaa7..a9ab33ed 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -593,7 +593,6 @@ var unsupportedExchangeNames = []string{ "testexch", "alphapoint", "bitflyer", // Bitflyer has many "ErrNotYetImplemented, which is true, but not what we care to test for here - "bittrex", // the api is about to expire in March, and we haven't updated it yet "itbit", // itbit has no way of retrieving pair data "btse", // TODO rm once timeout issues resolved "poloniex", // outdated API // TODO rm once updated diff --git a/config_example.json b/config_example.json index a1544fda..fe8426d2 100644 --- a/config_example.json +++ b/config_example.json @@ -956,84 +956,6 @@ } ] }, - { - "name": "Bittrex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "USDT-BTC", - "available": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-EMC2,BTC-XMY,BTC-GRS,BTC-NLG,BTC-MONA,BTC-VRC,BTC-CURE,BTC-XMR,BTC-XDN,BTC-NAV,BTC-XST,BTC-AR,BTC-VIA,BTC-PINK,BTC-IOC,BTC-SYS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-BLOCK,BTC-BTS,BTC-XRP,BTC-GAME,BTC-NXS,BTC-GEO,BTC-FLO,BTC-MUE,BTC-XEM,BTC-SPHR,BTC-OK,BTC-AEON,BTC-ETH,BTC-EXP,BTC-XLM,USDT-BTC,BTC-FCT,BTC-MAID,BTC-SLS,BTC-RADS,BTC-DCR,BTC-XVG,BTC-PIVX,BTC-MEME,BTC-STEEM,BTC-LSK,BTC-WAVES,BTC-LBC,BTC-SBD,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-REP,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-UBQ,BTC-KMD,BTC-SIB,BTC-ION,BTC-CRW,BTC-ARK,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-EDG,BTC-MORE,ETH-GNT,ETH-REP,USDT-ETH,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,ETH-GNO,BTC-HMQ,BTC-ANT,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-QRL,BTC-PTOY,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,BTC-PAY,ETH-PAY,BTC-MTL,BTC-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCH,USDT-BCH,BTC-BCH,BTC-DNT,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,USDT-OMG,BTC-ADA,BTC-MANA,ETH-MANA,BTC-RCN,BTC-VIB,ETH-VIB,BTC-MER,BTC-POWR,ETH-POWR,ETH-ADA,BTC-ENG,ETH-ENG,USDT-ADA,USDT-XVG,BTC-UKG,ETH-UKG,BTC-IGNIS,BTC-SRN,ETH-SRN,BTC-WAXP,ETH-WAXP,BTC-ZRX,ETH-ZRX,BTC-VEE,BTC-TRX,ETH-TRX,BTC-TUSD,BTC-LRC,ETH-TUSD,BTC-DMT,ETH-DMT,USDT-TUSD,USDT-SC,USDT-TRX,BTC-STMX,ETH-STMX,BTC-AID,BTC-NGC,BTC-GTO,USDT-DCR,USD-BTC,USD-USDT,USD-TUSD,BTC-TUBE,BTC-CMCT,USD-ETH,BTC-NLC2,BTC-MFT,BTC-LOOM,BTC-RFR,USDT-DGB,BTC-RVN,USD-XRP,USD-ETC,BTC-BFT,BTC-GO,BTC-HYDRO,BTC-UPP,USD-ADA,USD-ZEC,USDT-DOGE,BTC-ENJ,BTC-MET,USD-LTC,USD-TRX,BTC-DTA,BTC-EDR,BTC-IHT,USD-BCH,BTC-XHV,USDT-ZRX,BTC-NPXS,BTC-PMA,USDT-BAT,USDT-RVN,BTC-PAL,USD-SC,BTC-PAX,BTC-ZIL,BTC-MOC,BTC-OST,BTC-SPC,BTC-MED,BTC-BSV,BTC-IOST,USDT-BSV,ETH-BSV,BTC-SOLVE,BTC-USDS,USDT-PMA,ETH-NPXS,USDT-NPXS,USD-ZRX,BTC-JNT,BTC-LBA,USD-BAT,USD-BSV,BTC-DENT,USD-USDS,BTC-DRGN,USD-PAX,BTC-VITE,BTC-IOTX,USD-DGB,BTC-BTM,BTC-ELF,BTC-QNT,BTC-BTU,USD-ZEN,BTC-SPND,BTC-BTT,BTC-NKN,USD-KMD,USDT-BTT,BTC-GRIN,BTC-CTXC,BTC-HXRO,BTC-META,USDT-GRIN,BTC-FSN,BTC-ANKR,USDT-XLM,BTC-TRAC,BTC-CRO,BTC-ONT,ETH-SOLVE,BTC-ONG,BTC-TTC,BTC-PTON,BTC-PI,ETH-ANKR,BTC-PLA,BTC-ART,BTC-ORBS,USDT-ENJ,BTC-VBK,BTC-BORA,BTC-CND,USDT-ONT,BTC-FX,ETH-FX,BTC-ATOM,USDT-ATOM,ETH-ATOM,BTC-OCEAN,USDT-OCEAN,BTC-BWX,BTC-VDX,USDT-VDX,ETH-VDX,BTC-COSM,BTC-LAMB,BTC-STPT,BTC-DAI,ETH-DAI,USDT-DAI,BTC-FNB,BTC-PROM,BTC-ABYSS,BTC-EOS,ETH-EOS,USDT-EOS,BTC-FXC,BTC-DUSK,BTC-URAC,BTC-BLOC,BTC-BRZ,BTC-TEMCO,BTC-SPIN,BTC-LUNA,BTC-CHR,BTC-TUDA,BTC-UTK,BTC-PXL,BTC-AKRO,BTC-TSHP,BTC-HEDG,BTC-MRPH,BTC-HBAR,ETH-HBAR,USD-HBAR,USDT-HBAR,BTC-PLG,BTC-VET,USDT-VET,BTC-SIX,BTC-WGP,BTC-APM,BTC-FLETA,USD-DCR,BTC-BLTV,BTC-HDAC,BTC-HYC,BTC-LINK,USD-EOS,BTC-APIX,BTC-XTZ,ETH-XTZ,USD-XTZ,USDT-XTZ,BTC-XTP,BTC-XSR,BTC-CTC,USD-ATOM,BTC-IOTA,ETH-LINK,USD-LINK,USDT-LINK,BTC-VRA,BTC-ABBC,BTC-FRSP,BTC-WICC,USDT-WICC,USDT-NMR,USD-DASH,USD-RVN,USD-DAI,BTC-VANY,BTC-BOA,BTC-CPC,BTC-CKB,USDT-CKB,BTC-MOF,USDT-MOF,USD-WAXP,USDT-WAXP,BTC-UPT,BTC-UPUSD,BTC-UPEUR,BTC-CVT,BTC-HBD,BTC-HIVE,USDT-CRO,BTC-SXP,BTC-ELAMA,BTC-STC,BTC-IRIS,USDT-IRIS,USDT-BOA,EUR-BTC,EUR-ETH,EUR-USDT,EUR-BSV,EUR-BCH,EUR-TRX,USDT-APM,USDT-HXRO,BTC-OGN,ETH-OGN,BTC-ALGO,BTC-OXT,USDT-OXT,BTC-ICX,BTC-USDC,ETH-USDC,USD-USDC,USDT-USDC,USDT-UPUSD,USDT-BRZ,BTC-XUC,BTC-MDT,USDT-MDT,BTC-REV,USDT-XUC,USDT-REV,BTC-UCT,USDT-UCT,BTC-YOU,USD-HIVE,USDT-HIVE,USD-ENJ,ETH-ENJ,BTC-HDAO,USDT-HDAO,BTC-DNA,USDT-DNA,USDT-SOLVE,BTC-CNTM,USDT-LBC,BTC-LOON,BTC-TNC,USDT-LOON,USD-ALGO,USDT-ALGO,BTC-UBT,ETH-UBT,BTC-DEP,USDT-DEP,EUR-USD,BTC-CELO,ETH-CELO,USD-CELO,USDT-CELO,USDT-CNTM,BTC-VID,BTC-HNS,ETH-HNS,USDT-HNS,BTC-PHNX,BTC-UTI,USD-SOLVE,BTC-4ART,USDT-4ART,BTC-VLX,USDT-VLX,ETH-MET,ETH-TRAC,USDT-TRAC,BTC-ME,BTC-DAWN,BTC-KDA,USDT-KDA" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, { "name": "BTSE", "enabled": true, diff --git a/docs/ADD_NEW_EXCHANGE.md b/docs/ADD_NEW_EXCHANGE.md index f40c3f08..554fef0d 100644 --- a/docs/ADD_NEW_EXCHANGE.md +++ b/docs/ADD_NEW_EXCHANGE.md @@ -202,7 +202,6 @@ Yes means supported, No means not yet implemented and NA means protocol unsuppor | Bithumb | Yes | NA | NA | | BitMEX | Yes | Yes | NA | | Bitstamp | Yes | Yes | No | -| Bittrex | Yes | Yes | NA | | BTCMarkets | Yes | No | NA | | BTSE | Yes | Yes | NA | | COINUT | Yes | Yes | NA | @@ -233,7 +232,6 @@ var Exchanges = []string{ "bithumb", "bitmex", "bitstamp", - "bittrex", "btc markets", "btse", "coinbasepro", diff --git a/docs/MULTICHAIN_TRANSFER_SUPPORT.md b/docs/MULTICHAIN_TRANSFER_SUPPORT.md index ccb33596..1a6feb44 100644 --- a/docs/MULTICHAIN_TRANSFER_SUPPORT.md +++ b/docs/MULTICHAIN_TRANSFER_SUPPORT.md @@ -50,7 +50,6 @@ $ ./gctcli withdrawcryptofunds --exchange=binance --currency=USDT --address=TJU9 | Bithumb | No | No | | | BitMEX | No | No | Supports BTC only | | Bitstamp | No | No | | -| Bittrex | No | No | NA | | BTCMarkets | No | No| NA | | BTSE | No | No | Only through website | | Bybit | Yes | Yes | | diff --git a/docs/OHLCV.md b/docs/OHLCV.md index 2565fe96..fce44526 100644 --- a/docs/OHLCV.md +++ b/docs/OHLCV.md @@ -73,7 +73,6 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti | Bitmex | | | Bitstamp | Y | | BTC Markets | Y | -| Bittrex | | | BTSE | Y | | Bybit | Y | | Coinbase Pro | Y | diff --git a/engine/helpers.go b/engine/helpers.go index 4e915e37..f5010f21 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -34,7 +34,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/bithumb" "github.com/thrasher-corp/gocryptotrader/exchanges/bitmex" "github.com/thrasher-corp/gocryptotrader/exchanges/bitstamp" - "github.com/thrasher-corp/gocryptotrader/exchanges/bittrex" "github.com/thrasher-corp/gocryptotrader/exchanges/btcmarkets" "github.com/thrasher-corp/gocryptotrader/exchanges/btse" "github.com/thrasher-corp/gocryptotrader/exchanges/bybit" @@ -1010,8 +1009,6 @@ func NewSupportedExchangeByName(name string) (exchange.IBotExchange, error) { return new(bitmex.Bitmex), nil case "bitstamp": return new(bitstamp.Bitstamp), nil - case "bittrex": - return new(bittrex.Bittrex), nil case "btc markets": return new(btcmarkets.BTCMarkets), nil case "btse": diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index d3a40cd8..e1423d9e 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -976,7 +976,7 @@ func (b *Binance) getMultiplier(ctx context.Context, isMaker bool) (float64, err return multiplier, nil } -// calculateTradingFee returns the fee for trading any currency on Bittrex +// calculateTradingFee returns the fee for trading any currency on Binance func calculateTradingFee(purchasePrice, amount, multiplier float64) float64 { return (multiplier / 100) * purchasePrice * amount } diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index a618b94b..13a76bd6 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -966,7 +966,7 @@ func getOfflineTradeFee(price, amount float64) float64 { return 0.000750 * price * amount } -// calculateTradingFee returns the fee for trading any currency on Bittrex +// calculateTradingFee returns the fee for trading any currency on Bitmex func calculateTradingFee(purchasePrice, amount float64, isMaker bool) float64 { var fee = 0.000750 if isMaker { diff --git a/exchanges/bittrex/README.md b/exchanges/bittrex/README.md deleted file mode 100644 index a52c48bf..00000000 --- a/exchanges/bittrex/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# GoCryptoTrader package Bittrex - - - - -[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml) -[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/bittrex) -[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) - - -This bittrex package is part of the GoCryptoTrader codebase. - -## This is still in active development - -You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). - -Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) - -## Bittrex Exchange - -### Current Features - -+ REST Support - -### Notes - -- Bittrex used to have reversed market names: btc-ltc. The v3 API changed this to the more widely accepted format with first the base pair and then the quote pair: ltc-btc. -- Asset names and market names are not case sensitive. - -### How to enable - -+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example) - -+ Individual package example below: - -```go - // Exchanges will be abstracted out in further updates and examples will be - // supplied then -``` - -### How to do REST public/private calls - -+ If enabled via "configuration".json file the exchange will be added to the -IBotExchange array in the ```go var bot Bot``` and you will only be able to use -the wrapper interface functions for accessing exchange data. View routines.go -for an example of integration usage with GoCryptoTrader. Rudimentary example -below: - -main.go -```go -var b exchange.IBotExchange - -for i := range bot.Exchanges { - if bot.Exchanges[i].GetName() == "Bittrex" { - b = bot.Exchanges[i] - } -} - -// Public calls - wrapper functions - -// Fetches current ticker information -tick, err := b.FetchTicker() -if err != nil { - // Handle error -} - -// Fetches current orderbook information -ob, err := b.FetchOrderbook() -if err != nil { - // Handle error -} - -// Private calls - wrapper functions - make sure your APIKEY and APISECRET are -// set and AuthenticatedAPISupport is set to true - -// Fetches current account information -accountInfo, err := b.GetAccountInfo() -if err != nil { - // Handle error -} -``` - -+ If enabled via individually importing package, rudimentary example below: - -```go -// Public calls - -// Fetches current ticker information -ticker, err := b.GetTicker() -if err != nil { - // Handle error -} - -// Fetches current orderbook information -ob, err := b.GetOrderBook() -if err != nil { - // Handle error -} - -// Private calls - make sure your APIKEY and APISECRET are set and -// AuthenticatedAPISupport is set to true - -// GetUserInfo returns account info -accountInfo, err := b.GetUserInfo(...) -if err != nil { - // Handle error -} - -// Submits an order and the exchange and returns its tradeID -tradeID, err := b.Trade(...) -if err != nil { - // Handle error -} -``` - -### Please click GoDocs chevron above to view current GoDoc information for this package - -## Contribution - -Please feel free to submit any pull requests or suggest any desired features to be added. - -When submitting a PR, please abide by our coding guidelines: - -+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). -+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. -+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). -+ Pull requests need to be based on and opened against the `master` branch. - -## Donations - - - -If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: - -***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc*** diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go deleted file mode 100644 index 3cc0f93c..00000000 --- a/exchanges/bittrex/bittrex.go +++ /dev/null @@ -1,488 +0,0 @@ -package bittrex - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "time" - - "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/kline" - "github.com/thrasher-corp/gocryptotrader/exchanges/request" -) - -// Bittrex is the overaching type across the bittrex methods -type Bittrex struct { - exchange.Base - WsSequenceOrders int64 - - obm *orderbookManager - tickerCache *TickerCache -} - -const ( - bittrexAPIRestURL = "https://api.bittrex.com/v3" - bittrexAPIDeprecatedURL = "https://bittrex.com/api/v1.1" - - // Public endpoints - getMarkets = "/markets" - getMarketSummaries = "/markets/summaries" - getTicker = "/markets/%s/ticker" - getTickers = "/markets/tickers" - getMarketSummary = "/markets/%s/summary" - getMarketTrades = "/markets/%s/trades" - getOrderbook = "/markets/%s/orderbook?depth=%s" - getRecentCandles = "/markets/%s/candles/%s/%s/recent" - getHistoricalCandles = "/markets/%s/candles/%s/%s/historical/%s" - getCurrencies = "/currencies" - - // Authenticated endpoints - getBalances = "/balances" - getBalance = "/balances/%s" - getDepositAddress = "/addresses/%s" - depositAddresses = "/addresses/" - getAllOpenOrders = "/orders/open" - getOpenOrders = "/orders/open?marketSymbol=%s" - getOrder = "/orders/%s" - getClosedOrders = "/orders/closed?marketSymbol=%s" - cancelOrder = "/orders/%s" - cancelOpenOrders = "/orders/open" - getClosedWithdrawals = "/withdrawals/closed" - getOpenWithdrawals = "/withdrawals/open" - submitWithdrawal = "/transfers" - getClosedDeposits = "/deposits/closed" - getOpenDeposits = "/deposits/open" - submitOrder = "/orders" - - // Other Consts - ratePeriod = time.Minute - rateLimit = 60 - orderbookDepth = 500 // ws uses REST snapshots and needs identical depths -) - -// GetMarkets is used to get the open and available trading markets at Bittrex -// along with other meta data. -func (b *Bittrex) GetMarkets(ctx context.Context) ([]MarketData, error) { - var resp []MarketData - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, getMarkets, &resp, nil) -} - -// GetCurrencies is used to get all supported currencies at Bittrex -func (b *Bittrex) GetCurrencies(ctx context.Context) ([]CurrencyData, error) { - var resp []CurrencyData - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, getCurrencies, &resp, nil) -} - -// GetTicker sends a public get request and returns current ticker information -// on the supplied currency. Example currency input param "ltc-btc". -func (b *Bittrex) GetTicker(ctx context.Context, marketName string) (TickerData, error) { - var resp TickerData - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getTicker, marketName), &resp, nil) -} - -// GetTickers returns bittrex tickers -func (b *Bittrex) GetTickers(ctx context.Context) ([]TickerData, error) { - var resp []TickerData - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, getTickers, &resp, nil) -} - -// GetMarketSummaries is used to get the last 24 hour summary of all active -// currencies -func (b *Bittrex) GetMarketSummaries(ctx context.Context) ([]MarketSummaryData, error) { - var resp []MarketSummaryData - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, getMarketSummaries, &resp, nil) -} - -// GetMarketSummary is used to get the last 24 hour summary of all active -// exchanges by currency pair (ltc-btc). -func (b *Bittrex) GetMarketSummary(ctx context.Context, marketName string) (MarketSummaryData, error) { - var resp MarketSummaryData - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getMarketSummary, marketName), &resp, nil) -} - -// GetOrderbook method returns current order book information by currency and depth. -// "marketSymbol" ie ltc-btc -// "depth" is either 1, 25 or 500. Server side, the depth defaults to 25. -func (b *Bittrex) GetOrderbook(ctx context.Context, marketName string, depth int64) (*OrderbookData, int64, error) { - strDepth := strconv.FormatInt(depth, 10) - - var resp OrderbookData - var sequence int64 - resultHeader := http.Header{} - err := b.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getOrderbook, marketName, strDepth), &resp, &resultHeader) - if err != nil { - return nil, 0, err - } - sequence, err = strconv.ParseInt(resultHeader.Get("sequence"), 10, 64) - if err != nil { - return nil, 0, err - } - - return &resp, sequence, nil -} - -// GetMarketHistory retrieves the latest trades that have occurred for a specific market -func (b *Bittrex) GetMarketHistory(ctx context.Context, currency string) ([]TradeData, error) { - var resp []TradeData - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getMarketTrades, currency), &resp, nil) -} - -// Order places an order -func (b *Bittrex) Order(ctx context.Context, marketName, side, orderType string, timeInForce TimeInForce, price, amount, ceiling float64) (OrderData, error) { - req := make(map[string]interface{}) - req["marketSymbol"] = marketName - req["direction"] = side - req["type"] = orderType - req["quantity"] = strconv.FormatFloat(amount, 'f', -1, 64) - if orderType == "CEILING_LIMIT" || orderType == "CEILING_MARKET" { - req["ceiling"] = strconv.FormatFloat(ceiling, 'f', -1, 64) - } - if orderType == "LIMIT" { - req["limit"] = strconv.FormatFloat(price, 'f', -1, 64) - } - if timeInForce != "" { - req["timeInForce"] = timeInForce - } else { - req["timeInForce"] = GoodTilCancelled - } - var resp OrderData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, submitOrder, nil, req, &resp, nil) -} - -// GetOpenOrders returns all orders that you currently have opened. -// A specific market can be requested for example "ltc-btc" -func (b *Bittrex) GetOpenOrders(ctx context.Context, marketName string) ([]OrderData, int64, error) { - var path string - if marketName == "" || marketName == " " { - path = getAllOpenOrders - } else { - path = fmt.Sprintf(getOpenOrders, marketName) - } - var resp []OrderData - var sequence int64 - resultHeader := http.Header{} - err := b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, &resp, &resultHeader) - if err != nil { - return nil, 0, err - } - sequence, err = strconv.ParseInt(resultHeader.Get("sequence"), 10, 64) - if err != nil { - return nil, 0, err - } - return resp, sequence, err -} - -// CancelExistingOrder is used to cancel a buy or sell order. -func (b *Bittrex) CancelExistingOrder(ctx context.Context, uuid string) (OrderData, error) { - var resp OrderData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, fmt.Sprintf(cancelOrder, uuid), nil, nil, &resp, nil) -} - -// CancelOpenOrders is used to cancel all open orders for a specific market -// Or cancel all orders for all markets if the parameter `markets` is set to "" -func (b *Bittrex) CancelOpenOrders(ctx context.Context, market string) ([]BulkCancelResultData, error) { - var resp []BulkCancelResultData - - params := url.Values{} - if market != "" { - params.Set("marketSymbol", market) - } - - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, cancelOpenOrders, params, nil, &resp, nil) -} - -// GetRecentCandles retrieves recent candles; -// Interval: MINUTE_1, MINUTE_5, HOUR_1, or DAY_1 -// Type: TRADE or MIDPOINT -func (b *Bittrex) GetRecentCandles(ctx context.Context, marketName, candleInterval, candleType string) ([]CandleData, error) { - var resp []CandleData - - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getRecentCandles, marketName, candleType, candleInterval), &resp, nil) -} - -// GetHistoricalCandles retrieves recent candles -// Type: TRADE or MIDPOINT -func (b *Bittrex) GetHistoricalCandles(ctx context.Context, marketName, candleInterval, candleType string, year, month, day int) ([]CandleData, error) { - var resp []CandleData - - var start string - switch candleInterval { - case "MINUTE_1", "MINUTE_5": - // Retrieve full day - start = fmt.Sprintf("%d/%d/%d", year, month, day) - case "HOUR_1": - // Retrieve full month - start = fmt.Sprintf("%d/%d", year, month) - case "DAY_1": - // Retrieve full year - start = fmt.Sprintf("%d", year) - default: - return resp, fmt.Errorf("%w %v", kline.ErrUnsupportedInterval, candleInterval) - } - - return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getHistoricalCandles, marketName, candleType, candleInterval, start), &resp, nil) -} - -// GetBalances is used to retrieve all balances from your account -func (b *Bittrex) GetBalances(ctx context.Context) ([]BalanceData, error) { - var resp []BalanceData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getBalances, nil, nil, &resp, nil) -} - -// GetAccountBalanceByCurrency is used to retrieve the balance from your account -// for a specific currency. ie. "btc" or "ltc" -func (b *Bittrex) GetAccountBalanceByCurrency(ctx context.Context, currency string) (BalanceData, error) { - var resp BalanceData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, fmt.Sprintf(getBalance, currency), nil, nil, &resp, nil) -} - -// GetCryptoDepositAddresses is used to retrieve all deposit addresses -func (b *Bittrex) GetCryptoDepositAddresses(ctx context.Context) ([]AddressData, error) { - var resp []AddressData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, depositAddresses, nil, nil, &resp, nil) -} - -// GetCryptoDepositAddress is used to retrieve an address for a specific currency -func (b *Bittrex) GetCryptoDepositAddress(ctx context.Context, currency string) (AddressData, error) { - var resp AddressData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, fmt.Sprintf(getDepositAddress, currency), nil, nil, &resp, nil) -} - -// ProvisionNewDepositAddress provisions a new deposit address for a specific currency -func (b *Bittrex) ProvisionNewDepositAddress(ctx context.Context, currency string) (*ProvisionNewAddressData, error) { - req := make(map[string]interface{}, 1) - req["currencySymbol"] = currency - var resp ProvisionNewAddressData - return &resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, depositAddresses, nil, req, &resp, nil) -} - -// Withdraw is used to withdraw funds from your account. -func (b *Bittrex) Withdraw(ctx context.Context, currency, paymentID, address string, quantity float64) (WithdrawalData, error) { - req := make(map[string]interface{}) - req["currencySymbol"] = currency - req["quantity"] = strconv.FormatFloat(quantity, 'f', -1, 64) - req["cryptoAddress"] = address - if len(paymentID) > 0 { - req["cryptoAddressTag"] = paymentID - } - var resp WithdrawalData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, submitWithdrawal, nil, req, &resp, nil) -} - -// GetOrder is used to retrieve a single order by UUID. -func (b *Bittrex) GetOrder(ctx context.Context, uuid string) (OrderData, error) { - var resp OrderData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, fmt.Sprintf(getOrder, uuid), nil, nil, &resp, nil) -} - -// GetOrderHistoryForCurrency is used to retrieve your order history. If marketName -// is omitted it will return the entire order History. -func (b *Bittrex) GetOrderHistoryForCurrency(ctx context.Context, currency string) ([]OrderData, error) { - var resp []OrderData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, fmt.Sprintf(getClosedOrders, currency), nil, nil, &resp, nil) -} - -// GetClosedWithdrawals is used to retrieve your withdrawal history. -func (b *Bittrex) GetClosedWithdrawals(ctx context.Context) ([]WithdrawalData, error) { - var resp []WithdrawalData - - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getClosedWithdrawals, nil, nil, &resp, nil) -} - -// GetClosedWithdrawalsForCurrency is used to retrieve your withdrawal history for the specified currency. -func (b *Bittrex) GetClosedWithdrawalsForCurrency(ctx context.Context, currency string) ([]WithdrawalData, error) { - var resp []WithdrawalData - - params := url.Values{} - params.Set("currencySymbol", currency) - - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getClosedWithdrawals, params, nil, &resp, nil) -} - -// GetOpenWithdrawals is used to retrieve your withdrawal history. If currency -// omitted it will return the entire history -func (b *Bittrex) GetOpenWithdrawals(ctx context.Context) ([]WithdrawalData, error) { - var resp []WithdrawalData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getOpenWithdrawals, nil, nil, &resp, nil) -} - -// GetClosedDeposits is used to retrieve your deposit history. -func (b *Bittrex) GetClosedDeposits(ctx context.Context) ([]DepositData, error) { - var resp []DepositData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getClosedDeposits, nil, nil, &resp, nil) -} - -// GetClosedDepositsForCurrency is used to retrieve your deposit history for the specified currency -func (b *Bittrex) GetClosedDepositsForCurrency(ctx context.Context, currency string) ([]DepositData, error) { - var resp []DepositData - - params := url.Values{} - params.Set("currencySymbol", currency) - - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getClosedDeposits, params, nil, &resp, nil) -} - -// GetClosedDepositsPaginated is used to retrieve your deposit history. -// The maximum page size is 200 and it defaults to 100. -// PreviousPageToken is the unique identifier of the item that the resulting -// query result should end before, in the sort order of the given endpoint. Used -// for traversing a paginated set in the reverse direction. -func (b *Bittrex) GetClosedDepositsPaginated(ctx context.Context, pageSize int, previousPageTokenOptional ...string) ([]DepositData, error) { - var resp []DepositData - - params := url.Values{} - params.Set("pageSize", strconv.Itoa(pageSize)) - - if len(previousPageTokenOptional) > 0 { - params.Set("previousPageToken", previousPageTokenOptional[0]) - } - - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getClosedDeposits, params, nil, &resp, nil) -} - -// GetOpenDeposits is used to retrieve your open deposits. -func (b *Bittrex) GetOpenDeposits(ctx context.Context) ([]DepositData, error) { - var resp []DepositData - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getOpenDeposits, nil, nil, &resp, nil) -} - -// GetOpenDepositsForCurrency is used to retrieve your open deposits for the specified currency -func (b *Bittrex) GetOpenDepositsForCurrency(ctx context.Context, currency string) ([]DepositData, error) { - var resp []DepositData - - params := url.Values{} - params.Set("currencySymbol", currency) - - return resp, b.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getOpenDeposits, params, nil, &resp, nil) -} - -// SendHTTPRequest sends an unauthenticated HTTP request -func (b *Bittrex) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}, resultHeader *http.Header) error { - endpoint, err := b.API.Endpoints.GetURL(ep) - if err != nil { - return err - } - item := &request.Item{ - Method: http.MethodGet, - Path: endpoint + path, - Result: result, - Verbose: b.Verbose, - HTTPDebugging: b.HTTPDebugging, - HTTPRecording: b.HTTPRecording, - HeaderResponse: resultHeader, - } - return b.SendPayload(ctx, request.Unset, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) -} - -// SendAuthHTTPRequest sends an authenticated request -func (b *Bittrex) SendAuthHTTPRequest(ctx context.Context, ep exchange.URL, method, action string, params url.Values, data, result interface{}, resultHeader *http.Header) error { - creds, err := b.GetCredentials(ctx) - if err != nil { - return err - } - endpoint, err := b.API.Endpoints.GetURL(ep) - if err != nil { - return err - } - - newRequest := func() (*request.Item, error) { - ts := strconv.FormatInt(time.Now().UnixMilli(), 10) - path := common.EncodeURLValues(action, params) - - var body io.Reader - var hmac, payload []byte - var contentHash string - if data == nil { - payload = []byte("") - } else { - var err error - payload, err = json.Marshal(data) - if err != nil { - return nil, err - } - } - body = bytes.NewBuffer(payload) - hash, err := crypto.GetSHA512(payload) - if err != nil { - return nil, err - } - contentHash = crypto.HexEncodeToString(hash) - sigPayload := ts + endpoint + path + method + contentHash - hmac, err = crypto.GetHMAC(crypto.HashSHA512, - []byte(sigPayload), - []byte(creds.Secret)) - if err != nil { - return nil, err - } - - headers := make(map[string]string) - headers["Api-Key"] = creds.Key - headers["Api-Timestamp"] = ts - headers["Api-Content-Hash"] = contentHash - headers["Api-Signature"] = crypto.HexEncodeToString(hmac) - headers["Content-Type"] = "application/json" - headers["Accept"] = "application/json" - - return &request.Item{ - Method: method, - Path: endpoint + path, - Headers: headers, - Body: body, - Result: result, - Verbose: b.Verbose, - HTTPDebugging: b.HTTPDebugging, - HTTPRecording: b.HTTPRecording, - HeaderResponse: resultHeader, - }, nil - } - - return b.SendPayload(ctx, request.Unset, newRequest, request.AuthenticatedRequest) -} - -// GetFee returns an estimate of fee based on type of transaction -func (b *Bittrex) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { - var fee float64 - var err error - - switch feeBuilder.FeeType { - case exchange.CryptocurrencyTradeFee: - fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount) - case exchange.CryptocurrencyWithdrawalFee: - fee, err = b.GetWithdrawalFee(ctx, feeBuilder.Pair.Base) - case exchange.OfflineTradeFee: - fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount) - } - if fee < 0 { - fee = 0 - } - return fee, err -} - -// GetWithdrawalFee returns the fee for withdrawing from the exchange -func (b *Bittrex) GetWithdrawalFee(ctx context.Context, c currency.Code) (float64, error) { - var fee float64 - - currencies, err := b.GetCurrencies(ctx) - if err != nil { - return 0, err - } - for i := range currencies { - if currencies[i].Symbol == c.String() { - fee = currencies[i].TxFee - } - } - return fee, nil -} - -// calculateTradingFee returns the fee for trading any currency on Bittrex -func calculateTradingFee(price, amount float64) float64 { - return 0.0025 * price * amount -} diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go deleted file mode 100644 index 347f2440..00000000 --- a/exchanges/bittrex/bittrex_test.go +++ /dev/null @@ -1,747 +0,0 @@ -package bittrex - -import ( - "context" - "errors" - "log" - "os" - "sync" - "testing" - "time" - - "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" - "github.com/thrasher-corp/gocryptotrader/core" - "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/kline" - "github.com/thrasher-corp/gocryptotrader/exchanges/order" - "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" -) - -// Please supply you own test keys here to run better tests. -const ( - apiKey = "" - apiSecret = "" - canManipulateRealOrders = false - currPair = "BTC-USDT" - curr = "BTC" -) - -var b = &Bittrex{} - -func TestMain(m *testing.M) { - b.SetDefaults() - cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json", true) - if err != nil { - log.Fatal(err) - } - bConfig, err := cfg.GetExchangeConfig("Bittrex") - if err != nil { - log.Fatal(err) - } - bConfig.API.Credentials.Key = apiKey - bConfig.API.Credentials.Secret = apiSecret - bConfig.API.AuthenticatedSupport = true - - err = b.Setup(bConfig) - if err != nil { - log.Fatal(err) - } - - if !b.IsEnabled() || !b.API.AuthenticatedSupport || - b.Verbose || len(b.BaseCurrencies) < 1 { - log.Fatal("Bittrex Setup values not set correctly") - } - - var wg sync.WaitGroup - err = b.Start(context.Background(), &wg) - if err != nil { - log.Fatal(err) - } - wg.Wait() - - os.Exit(m.Run()) -} - -func TestGetMarkets(t *testing.T) { - t.Parallel() - _, err := b.GetMarkets(context.Background()) - if err != nil { - t.Error(err) - } -} - -func TestGetCurrencies(t *testing.T) { - t.Parallel() - _, err := b.GetCurrencies(context.Background()) - if err != nil { - t.Error(err) - } -} - -func TestGetTicker(t *testing.T) { - t.Parallel() - _, err := b.GetTicker(context.Background(), currPair) - if err != nil { - t.Error(err) - } -} - -func TestGetMarketSummaries(t *testing.T) { - t.Parallel() - _, err := b.GetMarketSummaries(context.Background()) - if err != nil { - t.Error(err) - } -} - -func TestGetMarketSummary(t *testing.T) { - t.Parallel() - _, err := b.GetMarketSummary(context.Background(), currPair) - if err != nil { - t.Error(err) - } -} - -func TestGetOrderbook(t *testing.T) { - t.Parallel() - - _, _, err := b.GetOrderbook(context.Background(), currPair, 500) - if err != nil { - t.Error(err) - } -} - -func TestGetMarketHistory(t *testing.T) { - t.Parallel() - - _, err := b.GetMarketHistory(context.Background(), currPair) - if err != nil { - t.Error(err) - } -} - -func TestGetRecentCandles(t *testing.T) { - t.Parallel() - - _, err := b.GetRecentCandles(context.Background(), - currPair, "HOUR_1", "MIDPOINT") - if err != nil { - t.Error(err) - } -} - -func TestGetHistoricalCandles(t *testing.T) { - t.Parallel() - - _, err := b.GetHistoricalCandles(context.Background(), - currPair, "MINUTE_5", "MIDPOINT", 2020, 12, 31) - if err != nil { - t.Error(err) - } - _, err = b.GetHistoricalCandles(context.Background(), - currPair, "MINUTE_5", "MIDPOINT", 2020, 12, 32) - if err == nil { - t.Error("invalid date should give an error") - } -} - -func TestOrder(t *testing.T) { - t.Parallel() - - _, err := b.Order(context.Background(), - currPair, order.Buy.String(), order.Limit.String(), "", 1, 1, 0.0) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetOpenOrders(t *testing.T) { - t.Parallel() - - _, _, err := b.GetOpenOrders(context.Background(), "") - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } - _, _, err = b.GetOpenOrders(context.Background(), currPair) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestCancelExistingOrder(t *testing.T) { - t.Parallel() - - _, err := b.CancelExistingOrder(context.Background(), "invalid-order") - if err == nil { - t.Error("Expected error") - } -} - -func TestGetAccountBalances(t *testing.T) { - t.Parallel() - - _, err := b.GetBalances(context.Background()) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetAccountBalanceByCurrency(t *testing.T) { - t.Parallel() - - _, err := b.GetAccountBalanceByCurrency(context.Background(), curr) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetOrder(t *testing.T) { - t.Parallel() - - _, err := b.GetOrder(context.Background(), "0cb4c4e4-bdc7-4e13-8c13-430e587d2cc1") - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } - _, err = b.GetOrder(context.Background(), "") - if sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetOrderHistoryForCurrency(t *testing.T) { - t.Parallel() - - _, err := b.GetOrderHistoryForCurrency(context.Background(), "") - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } - _, err = b.GetOrderHistoryForCurrency(context.Background(), currPair) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetClosedWithdrawals(t *testing.T) { - t.Parallel() - - _, err := b.GetClosedWithdrawals(context.Background()) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetClosedWithdrawalsForCurrency(t *testing.T) { - t.Parallel() - - _, err := b.GetClosedWithdrawalsForCurrency(context.Background(), curr) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetOpenWithdrawals(t *testing.T) { - t.Parallel() - - _, err := b.GetOpenWithdrawals(context.Background()) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetCryptoDepositAddresses(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, b) - _, err := b.GetCryptoDepositAddresses(context.Background()) - if err != nil { - t.Error(err) - } -} - -func TestProvisionNewDepositAddress(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, b) - _, err := b.ProvisionNewDepositAddress(context.Background(), currency.XRP.String()) - if err != nil { - t.Error(err) - } -} - -func TestGetClosedDeposits(t *testing.T) { - t.Parallel() - - _, err := b.GetClosedDeposits(context.Background()) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetClosedDepositsForCurrency(t *testing.T) { - t.Parallel() - - _, err := b.GetClosedDepositsForCurrency(context.Background(), curr) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetClosedDepositsPaginated(t *testing.T) { - t.Parallel() - - _, err := b.GetClosedDepositsPaginated(context.Background(), 100) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetOpenDeposits(t *testing.T) { - t.Parallel() - - _, err := b.GetOpenDeposits(context.Background()) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestGetOpenDepositsForCurrency(t *testing.T) { - t.Parallel() - - _, err := b.GetOpenDepositsForCurrency(context.Background(), curr) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Error(err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expected error") - } -} - -func TestWithdraw(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) - _, err := b.Withdraw(context.Background(), - curr, "", core.BitcoinDonationAddress, 0.0009) - if err != nil { - t.Error(err) - } -} - -func setFeeBuilder() *exchange.FeeBuilder { - return &exchange.FeeBuilder{ - Amount: 1, - FeeType: exchange.CryptocurrencyTradeFee, - Pair: currency.NewPair(currency.BTC, currency.LTC), - PurchasePrice: 1, - } -} - -// TestGetFeeByTypeOfflineTradeFee logic test -func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { - var feeBuilder = setFeeBuilder() - _, err := b.GetFeeByType(context.Background(), feeBuilder) - if err != nil { - t.Fatal(err) - } - if !sharedtestvalues.AreAPICredentialsSet(b) { - if feeBuilder.FeeType != exchange.OfflineTradeFee { - t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) - } - } else { - if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { - t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) - } - } -} - -func TestGetFee(t *testing.T) { - var feeBuilder = setFeeBuilder() - // CryptocurrencyTradeFee Basic - if _, err := b.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - - // CryptocurrencyTradeFee High quantity - feeBuilder = setFeeBuilder() - feeBuilder.Amount = 1000 - feeBuilder.PurchasePrice = 1000 - if _, err := b.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - - // CryptocurrencyTradeFee IsMaker - feeBuilder = setFeeBuilder() - feeBuilder.IsMaker = true - if _, err := b.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - - // CryptocurrencyTradeFee Negative purchase price - feeBuilder = setFeeBuilder() - feeBuilder.PurchasePrice = -1000 - if _, err := b.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - - // CryptocurrencyWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if _, err := b.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - - // CryptocurrencyDepositFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyDepositFee - if _, err := b.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - - // InternationalBankDepositFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.InternationalBankDepositFee - feeBuilder.FiatCurrency = currency.HKD - if _, err := b.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - - // InternationalBankWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee - feeBuilder.FiatCurrency = currency.HKD - if _, err := b.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } -} - -func TestFormatWithdrawPermissions(t *testing.T) { - expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.NoFiatWithdrawalsText - withdrawPermissions := b.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { - t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) - } -} - -func TestGetActiveOrders(t *testing.T) { - p, err := currency.NewPairFromString(currPair) - if err != nil { - t.Fatal(err) - } - - var getOrdersRequest = order.MultiOrderRequest{ - Type: order.AnyType, - Pairs: []currency.Pair{p}, - AssetType: asset.Spot, - Side: order.AnySide, - } - - getOrdersRequest.Pairs[0].Delimiter = currency.DashDelimiter - - _, err = b.GetActiveOrders(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} - -func TestGetOrderHistory(t *testing.T) { - var getOrdersRequest = order.MultiOrderRequest{ - Type: order.AnyType, - AssetType: asset.Spot, - Side: order.AnySide, - } - - _, err := b.GetOrderHistory(context.Background(), &getOrdersRequest) - if err == nil { - t.Error("Expected: 'At least one currency is required to fetch order history'. received nil") - } - - getOrdersRequest.Pairs = []currency.Pair{ - currency.NewPair(currency.BTC, currency.USDT), - } - - _, err = b.GetOrderHistory(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} - -// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them -// ---------------------------------------------------------------------------------------------------------------------------- - -func TestSubmitOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, b, canManipulateRealOrders) - - var orderSubmission = &order.Submit{ - Exchange: b.GetName(), - Pair: currency.Pair{ - Delimiter: currency.DashDelimiter, - Base: currency.BTC, - Quote: currency.LTC, - }, - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 1, - ClientID: "meowOrder", - AssetType: asset.Spot, - } - response, err := b.SubmitOrder(context.Background(), orderSubmission) - if sharedtestvalues.AreAPICredentialsSet(b) && (err != nil || response.Status != order.New) { - t.Errorf("Order failed to be placed: %v", err) - } else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} - -func TestCancelExchangeOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, b, canManipulateRealOrders) - - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: currencyPair, - AssetType: asset.Spot, - } - - err := b.CancelOrder(context.Background(), orderCancellation) - if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } -} - -func TestCancelAllExchangeOrders(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, b, canManipulateRealOrders) - - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: currencyPair, - AssetType: asset.Spot, - } - - resp, err := b.CancelAllOrders(context.Background(), orderCancellation) - - if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } - - if len(resp.Status) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.Status)) - } -} - -func TestModifyOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, b, canManipulateRealOrders) - _, err := b.ModifyOrder(context.Background(), - &order.Modify{AssetType: asset.Spot}) - if err == nil { - t.Error("Expected error") - } -} - -func TestWithdrawCryptocurrencyFunds(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, b, canManipulateRealOrders) - - withdrawCryptoRequest := withdraw.Request{ - Exchange: b.Name, - Amount: -1, - Currency: currency.BTC, - Description: "WITHDRAW IT ALL", - Crypto: withdraw.CryptoRequest{ - Address: core.BitcoinDonationAddress, - }, - } - - _, err := b.WithdrawCryptocurrencyFunds(context.Background(), &withdrawCryptoRequest) - if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(b) && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} - -func TestWithdrawFiat(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, b, canManipulateRealOrders) - - var withdrawFiatRequest = withdraw.Request{} - - _, err := b.WithdrawFiatFunds(context.Background(), &withdrawFiatRequest) - if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) - } -} - -func TestWithdrawInternationalBank(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, b, canManipulateRealOrders) - - var withdrawFiatRequest = withdraw.Request{} - - _, err := b.WithdrawFiatFundsToInternationalBank(context.Background(), - &withdrawFiatRequest) - if err != common.ErrFunctionNotSupported { - t.Errorf("Expected '%v', received: '%v'", common.ErrFunctionNotSupported, err) - } -} - -func TestGetDepositAddress(t *testing.T) { - if sharedtestvalues.AreAPICredentialsSet(b) { - _, err := b.GetDepositAddress(context.Background(), currency.XRP, "", "") - if err != nil { - t.Error(err) - } - } else { - _, err := b.GetDepositAddress(context.Background(), currency.BTC, "", "") - if err == nil { - t.Error("error cannot be nil") - } - } -} - -func TestGetRecentTrades(t *testing.T) { - t.Parallel() - currencyPair, err := currency.NewPairFromString(currPair) - if err != nil { - t.Fatal(err) - } - _, err = b.GetRecentTrades(context.Background(), currencyPair, asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestGetHistoricTrades(t *testing.T) { - t.Parallel() - currencyPair, err := currency.NewPairFromString(currPair) - if err != nil { - t.Fatal(err) - } - _, err = b.GetHistoricTrades(context.Background(), - currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now()) - if err != nil && err != common.ErrFunctionNotSupported { - t.Fatal(err) - } -} - -func TestGetHistoricCandles(t *testing.T) { - t.Parallel() - pair, err := currency.NewPairFromString("btc-usdt") - if err != nil { - t.Fatal(err) - } - - start := time.Unix(1546300800, 0) - end := start.AddDate(0, 12, 0) - _, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, start, end) - if err != nil { - t.Fatal(err) - } - - end = time.Now() - start = end.AddDate(0, -12, 0) - _, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, start, end) - if err != nil { - t.Fatal(err) - } - - start = end.AddDate(0, 0, -30) - _, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneHour, start, end) - if err != nil { - t.Fatal(err) - } - - end = time.Now().Add(-kline.OneDay.Duration()) - start = end.AddDate(0, 0, -1).Add(time.Minute * 5) - _, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.FiveMin, start, end) - if err != nil { - t.Fatal(err) - } -} - -func TestGetHistoricCandlesExtended(t *testing.T) { - t.Parallel() - pair, err := currency.NewPairFromString("btc-usdt") - if err != nil { - t.Fatal(err) - } - start := time.Unix(1546300800, 0) - end := time.Unix(1577836799, 0) - _, err = b.GetHistoricCandlesExtended(context.Background(), pair, asset.Spot, kline.OneDay, start, end) - if !errors.Is(err, common.ErrFunctionNotSupported) { - t.Fatal(err) - } -} - -func TestGetTickers(t *testing.T) { - t.Parallel() - _, err := b.GetTickers(context.Background()) - if err != nil { - t.Error(err) - } -} - -func TestUpdateTickers(t *testing.T) { - t.Parallel() - err := b.UpdateTickers(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } - err = b.UpdateTickers(context.Background(), asset.Futures) - if !errors.Is(err, asset.ErrNotSupported) { - t.Fatal(err) - } -} diff --git a/exchanges/bittrex/bittrex_types.go b/exchanges/bittrex/bittrex_types.go deleted file mode 100644 index da217efd..00000000 --- a/exchanges/bittrex/bittrex_types.go +++ /dev/null @@ -1,305 +0,0 @@ -package bittrex - -import ( - "sync" - "time" - - "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/stream" -) - -// CancelOrderRequest holds request data for CancelOrder -type CancelOrderRequest struct { - OrderID int64 `json:"orderId,string"` -} - -// TimeInForce defines timeInForce types -type TimeInForce string - -// All order status types -const ( - GoodTilCancelled TimeInForce = "GOOD_TIL_CANCELLED" - ImmediateOrCancel TimeInForce = "IMMEDIATE_OR_CANCEL" - FillOrKill TimeInForce = "FILL_OR_KILL" - PostOnlyGoodTilCancelled TimeInForce = "POST_ONLY_GOOD_TIL_CANCELLED" - BuyNow TimeInForce = "BUY_NOW" -) - -// OrderData holds order data -type OrderData struct { - ID string `json:"id"` - MarketSymbol string `json:"marketSymbol"` - Direction string `json:"direction"` - Type string `json:"type"` - Quantity float64 `json:"quantity,string"` - Limit float64 `json:"limit,string"` - Ceiling float64 `json:"ceiling,string"` - TimeInForce string `json:"timeInForce"` - ClientOrderID string `json:"clientOrderId"` - FillQuantity float64 `json:"fillQuantity,string"` - Commission float64 `json:"commission,string"` - Proceeds float64 `json:"proceeds,string"` - Status string `json:"status"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - ClosedAt time.Time `json:"closedAt"` - OrderToCancel struct { - Type string `json:"type,string"` - ID string `json:"id,string"` - } `json:"orderToCancel"` -} - -// BulkCancelResultData holds the result of a bulk cancel action -type BulkCancelResultData struct { - ID string `json:"id"` - StatusCode string `json:"statusCode"` - Result OrderData `json:"result"` -} - -// MarketData stores market data -type MarketData struct { - Symbol string `json:"symbol"` - BaseCurrencySymbol string `json:"baseCurrencySymbol"` - QuoteCurrencySymbol string `json:"quoteCurrencySymbol"` - MinTradeSize float64 `json:"minTradeSize,string"` - Precision int32 `json:"precision"` - Status string `json:"status"` - CreatedAt time.Time `json:"createdAt"` - Notice string `json:"notice"` - ProhibitedIn []string `json:"prohibitedIn"` -} - -// TickerData stores ticker data -type TickerData struct { - Symbol string `json:"symbol"` - LastTradeRate float64 `json:"lastTradeRate,string"` - BidRate float64 `json:"bidRate,string"` - AskRate float64 `json:"askRate,string"` - UpdatedAt time.Time `json:"updatedAt"` -} - -// TradeData stores trades data -type TradeData struct { - ID string `json:"id"` - ExecutedAt time.Time `json:"executedAt"` - Quantity float64 `json:"quantity,string"` - Rate float64 `json:"rate,string"` - TakerSide string `json:"takerSide"` -} - -// MarketSummaryData stores market summary data -type MarketSummaryData struct { - Symbol string `json:"symbol"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Volume float64 `json:"volume,string"` - QuoteVolume float64 `json:"quoteVolume,string"` - PercentChange float64 `json:"percentChange,string"` - UpdatedAt time.Time `json:"updatedAt"` -} - -// OrderbookData holds the order book data -type OrderbookData struct { - Bid []OrderbookEntryData `json:"bid"` - Ask []OrderbookEntryData `json:"ask"` -} - -// OrderbookEntryData holds an order book entry -type OrderbookEntryData struct { - Quantity float64 `json:"quantity,string"` - Rate float64 `json:"rate,string"` -} - -// BalanceData holds balance data -type BalanceData struct { - CurrencySymbol string `json:"currencySymbol"` - Total float64 `json:"total,string"` - Available float64 `json:"available,string"` - UpdatedAt time.Time `json:"updatedAt"` -} - -// AddressData holds address data -// Status is REQUESTED or PROVISIONED -type AddressData struct { - Status string `json:"status"` - CurrencySymbol string `json:"currencySymbol"` - CryptoAddress string `json:"cryptoAddress"` - CryptoAddressTag string `json:"cryptoAddressTag"` -} - -// ProvisionNewAddressData holds the provision deposit data -// Status is REQUESTED -type ProvisionNewAddressData struct { - Status string `json:"status"` - CurrencySymbol string `json:"currencySymbol"` -} - -// CurrencyData holds currency data -// Status is ONLINE or OFFLINE -type CurrencyData struct { - Symbol string `json:"symbol"` - Name string `json:"name"` - CoinType string `json:"coinType"` - Status string `json:"status"` - MinConfirmations int32 `json:"minConfirmations"` - Notice string `json:"notice"` - TxFee float64 `json:"txFee,string"` - LogoURL string `json:"logoUrl"` - ProhibitedIn []string `json:"prohibitedIn"` -} - -// WithdrawalData holds withdrawal data -type WithdrawalData struct { - ID string `json:"id"` - CurrencySymbol string `json:"currencySymbol"` - Quantity float64 `json:"quantity,string"` - CryptoAddress string `json:"cryptoAddress"` - CryptoAddressTag string `json:"cryptoAddressTag"` - TxCost float64 `json:"txCost,string"` - TxID string `json:"txId"` - Status string `json:"status"` - CreatedAt time.Time `json:"createdAt"` - CompletedAt time.Time `json:"completedAt"` - ClientWithdrawalID string `json:"clientWithdrawalId"` -} - -// DepositData holds deposit data -type DepositData struct { - ID string `json:"id"` - CurrencySymbol string `json:"currencySymbol"` - Quantity float64 `json:"quantity,string"` - CryptoAddress string `json:"cryptoAddress"` - CryptoAddressTag string `json:"cryptoAddressTag"` - TxID string `json:"txId"` - Confirmations int32 `json:"confirmations"` - UpdatedAt time.Time `json:"updatedAt"` - CompletedAt time.Time `json:"completedAt"` - Status string `json:"status"` - Source string `json:"source"` -} - -// CandleData holds candle data -type CandleData struct { - StartsAt time.Time `json:"startsAt"` - Open float64 `json:"open,string"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Close float64 `json:"close,string"` - Volume float64 `json:"volume,string"` - QuoteVolume float64 `json:"quoteVolume,string"` -} - -// WsSignalRHandshakeData holds data for the SignalR websocket wrapper handshake -type WsSignalRHandshakeData struct { - URL string `json:"Url"` // Path to the SignalR endpoint - ConnectionToken string `json:"ConnectionToken"` // Connection token assigned by the server - ConnectionID string `json:"ConnectionId"` // The ID of the connection - KeepAliveTimeout float64 `json:"KeepAliveTimeout"` // Representing the amount of time to wait before sending a keep alive packet over an idle connection - DisconnectTimeout float64 `json:"DisconnectTimeout"` // Represents the amount of time to wait after a connection goes away before raising the disconnect event - ConnectionTimeout float64 `json:"ConnectionTimeout"` // Represents the amount of time to leave a connection open before timing out - TryWebSockets bool `json:"TryWebSockets"` // Whether the server supports websockets - ProtocolVersion string `json:"ProtocolVersion"` // The version of the protocol used for communication - TransportConnectTimeout float64 `json:"TransportConnectTimeout"` // The maximum amount of time the client should try to connect to the server using a given transport - LongPollDelay float64 `json:"LongPollDelay"` // The time to tell the browser to wait before reestablishing a long poll connection after data is sent from the server. -} - -// WsEventRequest holds data on websocket requests -type WsEventRequest struct { - Hub string `json:"H"` - Method string `json:"M"` - Arguments interface{} `json:"A"` - InvocationID int64 `json:"I"` -} - -// WsEventStatus holds data on the websocket event status -type WsEventStatus struct { - Success bool `json:"Success"` - ErrorCode string `json:"ErrorCode"` -} - -// WsEventResponse holds data on the websocket response -type WsEventResponse struct { - C string `json:"C"` - S int `json:"S"` - G string `json:"G"` - Response interface{} `json:"R"` - InvocationID int64 `json:"I,string"` - Message []struct { - Hub string `json:"H"` - Method string `json:"M"` - Arguments []string `json:"A"` - } `json:"M"` -} - -// WsSubscriptionResponse holds data on the websocket response -type WsSubscriptionResponse struct { - C string `json:"C"` - S int `json:"S"` - G string `json:"G"` - Response []WsEventStatus `json:"R"` - InvocationID int64 `json:"I,string"` - Message []struct { - Hub string `json:"H"` - Method string `json:"M"` - Arguments []string `json:"A"` - } `json:"M"` -} - -// WsAuthResponse holds data on the websocket response -type WsAuthResponse struct { - C string `json:"C"` - S int `json:"S"` - G string `json:"G"` - Response WsEventStatus `json:"R"` - InvocationID int64 `json:"I,string"` - Message []struct { - Hub string `json:"H"` - Method string `json:"M"` - Arguments []string `json:"A"` - } `json:"M"` -} - -// OrderbookUpdateMessage holds websocket orderbook update messages -type OrderbookUpdateMessage struct { - MarketSymbol string `json:"marketSymbol"` - Depth int `json:"depth"` - Sequence int64 `json:"sequence"` - BidDeltas []OrderbookEntryData `json:"bidDeltas"` - AskDeltas []OrderbookEntryData `json:"askDeltas"` -} - -// OrderUpdateMessage holds websocket order update messages -type OrderUpdateMessage struct { - AccountID string `json:"accountId"` - Sequence int `json:"int,string"` - Delta OrderData `json:"delta"` -} - -// WsPendingRequest holds pending requests -type WsPendingRequest struct { - WsEventRequest - ChannelsToSubscribe *[]stream.ChannelSubscription -} - -// orderbookManager defines a way of managing and maintaining synchronisation -// across connections and assets. -type orderbookManager struct { - state map[currency.Code]map[currency.Code]map[asset.Item]*update - sync.Mutex - - jobs chan job -} - -type update struct { - buffer chan *OrderbookUpdateMessage - fetchingBook bool - initialSync bool - needsFetchingBook bool -} - -// job defines a synchronisation job that tells a go routine to fetch an -// orderbook via the REST protocol -type job struct { - Pair currency.Pair -} diff --git a/exchanges/bittrex/bittrex_websocket.go b/exchanges/bittrex/bittrex_websocket.go deleted file mode 100644 index 3cb46137..00000000 --- a/exchanges/bittrex/bittrex_websocket.go +++ /dev/null @@ -1,624 +0,0 @@ -package bittrex - -import ( - "bytes" - "compress/flate" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "sync" - "time" - - "github.com/gofrs/uuid" - "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/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/stream" - "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/log" -) - -const ( - bittrexAPIWSURL = "wss://socket-v3.bittrex.com/signalr" - bittrexAPIWSNegotiationsURL = "https://socket-v3.bittrex.com/signalr" - - bittrexWebsocketTimer = 13 * time.Second - wsTicker = "ticker" - wsOrderbook = "orderbook" - wsMarketSummary = "market_summary" - wsOrders = "order" - wsHeartbeat = "heartbeat" - authenticate = "Authenticate" - subscribe = "subscribe" - unsubscribe = "unsubscribe" - wsRateLimit = 50 - wsMessageRateLimit = 60 -) - -var defaultSpotSubscribedChannels = []string{ - // wsHeartbeat, - wsOrderbook, - wsTicker, - wsMarketSummary, -} - -var defaultSpotSubscribedChannelsAuth = []string{ - wsOrders, -} - -// TickerCache holds ticker and market summary data -// in order to combine them when processing data -type TickerCache struct { - MarketSummaries map[string]*MarketSummaryData - Tickers map[string]*TickerData - mu sync.RWMutex -} - -// WsConnect connects to a websocket feed -func (b *Bittrex) WsConnect() error { - if !b.Websocket.IsEnabled() || !b.IsEnabled() { - return errors.New(stream.WebsocketNotEnabled) - } - - var wsHandshakeData WsSignalRHandshakeData - err := b.WsSignalRHandshake(context.TODO(), &wsHandshakeData) - if err != nil { - return err - } - - var dialer websocket.Dialer - endpoint, err := b.API.Endpoints.GetURL(exchange.WebsocketSpot) - if err != nil { - return err - } - - params := url.Values{} - params.Set("clientProtocol", "1.5") - params.Set("transport", "webSockets") - params.Set("connectionToken", wsHandshakeData.ConnectionToken) - params.Set("connectionData", "[{name:\"c3\"}]") - params.Set("tid", "10") - - path := common.EncodeURLValues("/connect", params) - - err = b.Websocket.SetWebsocketURL(endpoint+path, false, false) - if err != nil { - return err - } - - err = b.Websocket.Conn.Dial(&dialer, http.Header{}) - if err != nil { - return err - } - // Can set up custom ping handler per websocket connection. - b.Websocket.Conn.SetupPingHandler(stream.PingHandler{ - MessageType: websocket.PingMessage, - Delay: bittrexWebsocketTimer, - }) - - // This reader routine is called prior to initiating a subscription for - // efficient processing. - b.Websocket.Wg.Add(1) - go b.wsReadData() - - b.setupOrderbookManager() - b.tickerCache = &TickerCache{ - MarketSummaries: make(map[string]*MarketSummaryData), - Tickers: make(map[string]*TickerData), - } - - if b.IsWebsocketAuthenticationSupported() { - err = b.WsAuth(context.TODO()) - if err != nil { - b.Websocket.DataHandler <- err - b.Websocket.SetCanUseAuthenticatedEndpoints(false) - } - } - return nil -} - -// WsSignalRHandshake requests the SignalR connection token over https -func (b *Bittrex) WsSignalRHandshake(ctx context.Context, result interface{}) error { - endpoint, err := b.API.Endpoints.GetURL(exchange.WebsocketSpotSupplementary) - if err != nil { - return err - } - path := "/negotiate?connectionData=[{name:\"c3\"}]&clientProtocol=1.5" - item := &request.Item{ - Method: http.MethodGet, - Path: endpoint + path, - Result: result, - Verbose: b.Verbose, - HTTPDebugging: b.HTTPDebugging, - HTTPRecording: b.HTTPRecording, - } - return b.SendPayload(ctx, request.Unset, func() (*request.Item, error) { - return item, nil - }, request.UnauthenticatedRequest) -} - -// WsAuth sends an authentication message to receive auth data -// Authentications expire after 10 minutes -func (b *Bittrex) WsAuth(ctx context.Context) error { - creds, err := b.GetCredentials(ctx) - if err != nil { - return err - } - // [apiKey, timestamp in ms, random uuid, signed payload] - apiKey := creds.Key - randomContent, err := uuid.NewV4() - if err != nil { - return err - } - timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10) - hmac, err := crypto.GetHMAC( - crypto.HashSHA512, - []byte(timestamp+randomContent.String()), - []byte(creds.Secret), - ) - if err != nil { - return err - } - - signature := crypto.HexEncodeToString(hmac) - - req := WsEventRequest{ - Hub: "c3", - Method: authenticate, - InvocationID: b.Websocket.Conn.GenerateMessageID(false), - } - - arguments := make([]string, 0) - arguments = append(arguments, apiKey, timestamp, randomContent.String(), signature) - req.Arguments = arguments - - requestString, err := json.Marshal(req) - if err != nil { - return err - } - if b.Verbose { - log.Debugf(log.WebsocketMgr, "%s Sending JSON message - %s\n", b.Name, requestString) - } - - respRaw, err := b.Websocket.Conn.SendMessageReturnResponse(req.InvocationID, req) - if err != nil { - return err - } - var response WsAuthResponse - err = json.Unmarshal(respRaw, &response) - if err != nil { - log.Warnf(log.WebsocketMgr, "%s - Cannot unmarshal into WsAuthResponse (%s)\n", b.Name, string(respRaw)) - return err - } - if !response.Response.Success { - log.Warnf(log.WebsocketMgr, "%s - Unable to authenticate (%s)", b.Name, response.Response.ErrorCode) - b.Websocket.SetCanUseAuthenticatedEndpoints(false) - } - return nil -} - -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be -// handled by ManageSubscriptions() -func (b *Bittrex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { - var subscriptions []stream.ChannelSubscription - pairs, err := b.GetEnabledPairs(asset.Spot) - if err != nil { - return nil, err - } - - channels := defaultSpotSubscribedChannels - if b.IsWebsocketAuthenticationSupported() { - channels = append(channels, defaultSpotSubscribedChannelsAuth...) - } - - for i := range pairs { - pair, err := b.FormatExchangeCurrency(pairs[i], asset.Spot) - if err != nil { - return nil, err - } - for y := range channels { - var channel string - switch channels[y] { - case wsOrderbook: - channel = channels[y] + "_" + pair.String() + "_" + strconv.FormatInt(orderbookDepth, 10) - case wsTicker: - channel = channels[y] + "_" + pair.String() - case wsMarketSummary: - channel = channels[y] + "_" + pair.String() - default: - channel = channels[y] - } - subscriptions = append(subscriptions, - stream.ChannelSubscription{ - Channel: channel, - Currency: pair, - Asset: asset.Spot, - }) - } - } - - return subscriptions, nil -} - -// Subscribe sends a websocket message to receive data from the channel -func (b *Bittrex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { - var x int - var errs error - for x = 0; x+wsMessageRateLimit < len(channelsToSubscribe); x += wsMessageRateLimit { - err := b.subscribeSlice(channelsToSubscribe[x : x+wsMessageRateLimit]) - if err != nil { - errs = common.AppendError(errs, err) - } - } - err := b.subscribeSlice(channelsToSubscribe[x:]) - if err != nil { - errs = common.AppendError(errs, err) - } - return errs -} - -func (b *Bittrex) subscribeSlice(channelsToSubscribe []stream.ChannelSubscription) error { - req := WsEventRequest{ - Hub: "c3", - Method: subscribe, - InvocationID: b.Websocket.Conn.GenerateMessageID(false), - } - - channels := make([]string, len(channelsToSubscribe)) - for i := range channelsToSubscribe { - channels[i] = channelsToSubscribe[i].Channel - } - arguments := make([][]string, 0) - arguments = append(arguments, channels) - req.Arguments = arguments - - requestString, err := json.Marshal(req) - if err != nil { - return err - } - if b.Verbose { - log.Debugf(log.WebsocketMgr, "%s - Sending JSON message - %s\n", b.Name, requestString) - } - respRaw, err := b.Websocket.Conn.SendMessageReturnResponse(req.InvocationID, req) - if err != nil { - return err - } - var response WsSubscriptionResponse - err = json.Unmarshal(respRaw, &response) - if err != nil { - return err - } - var errs error - for i := range response.Response { - if !response.Response[i].Success { - errs = common.AppendError(errs, errors.New("unable to subscribe to "+channels[i]+" - error code "+response.Response[i].ErrorCode)) - continue - } - b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) - } - return errs -} - -// Unsubscribe sends a websocket message to receive data from the channel -func (b *Bittrex) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { - var x int - var errs error - for x = 0; x+wsMessageRateLimit < len(channelsToUnsubscribe); x += wsMessageRateLimit { - err := b.unsubscribeSlice(channelsToUnsubscribe[x : x+wsMessageRateLimit]) - if err != nil { - errs = common.AppendError(errs, err) - } - } - err := b.unsubscribeSlice(channelsToUnsubscribe[x:]) - if err != nil { - errs = common.AppendError(errs, err) - } - return errs -} - -func (b *Bittrex) unsubscribeSlice(channelsToUnsubscribe []stream.ChannelSubscription) error { - req := WsEventRequest{ - Hub: "c3", - Method: unsubscribe, - InvocationID: b.Websocket.Conn.GenerateMessageID(false), - } - - channels := make([]string, len(channelsToUnsubscribe)) - for i := range channelsToUnsubscribe { - channels[i] = channelsToUnsubscribe[i].Channel - } - arguments := make([][]string, 0) - arguments = append(arguments, channels) - req.Arguments = arguments - - requestString, err := json.Marshal(req) - if err != nil { - return err - } - if b.Verbose { - log.Debugf(log.WebsocketMgr, "%s - Sending JSON message - %s\n", b.Name, requestString) - } - respRaw, err := b.Websocket.Conn.SendMessageReturnResponse(req.InvocationID, req) - if err != nil { - return err - } - var response WsSubscriptionResponse - err = json.Unmarshal(respRaw, &response) - if err != nil { - return err - } - var errs error - for i := range response.Response { - if !response.Response[i].Success { - errs = common.AppendError(errs, errors.New("unable to unsubscribe from "+channels[i]+" - error code "+response.Response[i].ErrorCode)) - continue - } - b.Websocket.RemoveSubscriptions(channelsToUnsubscribe[i]) - } - return errs -} - -// wsReadData gets and passes on websocket messages for processing -func (b *Bittrex) wsReadData() { - defer b.Websocket.Wg.Done() - - for { - select { - case <-b.Websocket.ShutdownC: - return - default: - resp := b.Websocket.Conn.ReadMessage() - if resp.Raw == nil { - log.Warnf(log.WebsocketMgr, "%s Received empty message\n", b.Name) - return - } - - err := b.wsHandleData(resp.Raw) - if err != nil { - b.Websocket.DataHandler <- err - } - } - } -} - -func (b *Bittrex) wsDecodeMessage(encodedMessage string, v interface{}) error { - raw, err := crypto.Base64Decode(encodedMessage) - if err != nil { - return err - } - reader := flate.NewReader(bytes.NewBuffer(raw)) - message, err := io.ReadAll(reader) - if err != nil { - return err - } - if err = reader.Close(); err != nil { - log.Warnf(log.WebsocketMgr, "%s wsDecodeMessage: unable to close reader: %s", - b.Name, - err, - ) - } - return json.Unmarshal(message, v) -} - -func (b *Bittrex) wsHandleData(respRaw []byte) error { - var response WsEventResponse - err := json.Unmarshal(respRaw, &response) - if err != nil { - log.Warnf(log.WebsocketMgr, "%s Cannot unmarshal into eventResponse (%s)\n", b.Name, string(respRaw)) - return err - } - if response.Response != nil && response.InvocationID > 0 { - if b.Websocket.Match.IncomingWithData(response.InvocationID, respRaw) { - return nil - } - return errors.New("received response to unknown request") - } - - if response.Response == nil && len(response.Message) == 0 && response.C == "" { - if b.Verbose { - log.Warnf(log.WebsocketMgr, "%s Received keep-alive (%s)\n", b.Name, string(respRaw)) - } - return nil - } - for i := range response.Message { - switch response.Message[i].Method { - case "orderBook": - for j := range response.Message[i].Arguments { - var orderbookUpdate OrderbookUpdateMessage - err = b.wsDecodeMessage(response.Message[i].Arguments[j], &orderbookUpdate) - if err != nil { - return err - } - var init bool - init, err = b.UpdateLocalOBBuffer(&orderbookUpdate) - if err != nil { - if init { - return nil - } - return fmt.Errorf("%v - UpdateLocalCache error: %s", - b.Name, - err) - } - } - case "ticker": - for j := range response.Message[i].Arguments { - var tickerUpdate TickerData - err = b.wsDecodeMessage(response.Message[i].Arguments[j], &tickerUpdate) - if err != nil { - return err - } - err = b.WsProcessUpdateTicker(tickerUpdate) - if err != nil { - return err - } - } - case "marketSummary": - for j := range response.Message[i].Arguments { - var marketSummaryUpdate MarketSummaryData - err = b.wsDecodeMessage(response.Message[i].Arguments[j], &marketSummaryUpdate) - if err != nil { - return err - } - - err = b.WsProcessUpdateMarketSummary(&marketSummaryUpdate) - if err != nil { - return err - } - } - case "heartbeat": - if b.Verbose { - log.Warnf(log.WebsocketMgr, "%s Received heartbeat\n", b.Name) - } - case "authenticationExpiring": - if b.Verbose { - log.Debugf(log.WebsocketMgr, "%s - Re-authenticating.\n", b.Name) - } - err = b.WsAuth(context.TODO()) - if err != nil { - b.Websocket.DataHandler <- err - b.Websocket.SetCanUseAuthenticatedEndpoints(false) - } - case "order": - for j := range response.Message[i].Arguments { - var orderUpdate OrderUpdateMessage - err = b.wsDecodeMessage(response.Message[i].Arguments[j], &orderUpdate) - if err != nil { - return err - } - err = b.WsProcessUpdateOrder(&orderUpdate) - if err != nil { - return err - } - } - } - } - return nil -} - -// WsProcessUpdateTicker processes an update on the ticker -func (b *Bittrex) WsProcessUpdateTicker(tickerData TickerData) error { - pair, err := currency.NewPairFromString(tickerData.Symbol) - if err != nil { - return err - } - - tickerPrice, err := ticker.GetTicker(b.Name, pair, asset.Spot) - if err != nil { - b.tickerCache.mu.Lock() - defer b.tickerCache.mu.Unlock() - if b.tickerCache.MarketSummaries[tickerData.Symbol] != nil { - marketSummaryData := b.tickerCache.MarketSummaries[tickerData.Symbol] - tickerPrice = b.constructTicker(tickerData, marketSummaryData, pair, asset.Spot) - b.Websocket.DataHandler <- tickerPrice - return nil - } - b.tickerCache.Tickers[tickerData.Symbol] = &tickerData - return nil - } - - tickerPrice.Last = tickerData.LastTradeRate - tickerPrice.Bid = tickerData.BidRate - tickerPrice.Ask = tickerData.AskRate - - b.Websocket.DataHandler <- tickerPrice - - return nil -} - -// WsProcessUpdateMarketSummary processes an update on the ticker -func (b *Bittrex) WsProcessUpdateMarketSummary(marketSummaryData *MarketSummaryData) error { - pair, err := currency.NewPairFromString(marketSummaryData.Symbol) - if err != nil { - return err - } - - tickerPrice, err := ticker.GetTicker(b.Name, pair, asset.Spot) - if err != nil { - b.tickerCache.mu.Lock() - defer b.tickerCache.mu.Unlock() - if b.tickerCache.Tickers[marketSummaryData.Symbol] != nil { - tickerData := b.tickerCache.Tickers[marketSummaryData.Symbol] - tickerPrice = b.constructTicker(*tickerData, marketSummaryData, pair, asset.Spot) - b.Websocket.DataHandler <- tickerPrice - return nil - } - b.tickerCache.MarketSummaries[marketSummaryData.Symbol] = marketSummaryData - return nil - } - - tickerPrice.High = marketSummaryData.High - tickerPrice.Low = marketSummaryData.Low - tickerPrice.Volume = marketSummaryData.Volume - tickerPrice.QuoteVolume = marketSummaryData.QuoteVolume - tickerPrice.LastUpdated = marketSummaryData.UpdatedAt - - b.Websocket.DataHandler <- tickerPrice - - return nil -} - -// WsProcessUpdateOrder processes an update on the open orders -func (b *Bittrex) WsProcessUpdateOrder(data *OrderUpdateMessage) error { - orderType, err := order.StringToOrderType(data.Delta.Type) - if err != nil { - b.Websocket.DataHandler <- order.ClassificationError{ - Exchange: b.Name, - OrderID: data.Delta.ID, - Err: err, - } - } - orderSide, err := order.StringToOrderSide(data.Delta.Direction) - if err != nil { - b.Websocket.DataHandler <- order.ClassificationError{ - Exchange: b.Name, - OrderID: data.Delta.ID, - Err: err, - } - } - orderStatus, err := order.StringToOrderStatus(data.Delta.Status) - if err != nil { - b.Websocket.DataHandler <- order.ClassificationError{ - Exchange: b.Name, - OrderID: data.Delta.ID, - Err: err, - } - } - - pair, err := currency.NewPairFromString(data.Delta.MarketSymbol) - if err != nil { - b.Websocket.DataHandler <- order.ClassificationError{ - Exchange: b.Name, - OrderID: data.Delta.ID, - Err: err, - } - } - - b.Websocket.DataHandler <- &order.Detail{ - ImmediateOrCancel: data.Delta.TimeInForce == string(ImmediateOrCancel), - FillOrKill: data.Delta.TimeInForce == string(GoodTilCancelled), - PostOnly: data.Delta.TimeInForce == string(PostOnlyGoodTilCancelled), - Price: data.Delta.Limit, - Amount: data.Delta.Quantity, - RemainingAmount: data.Delta.Quantity - data.Delta.FillQuantity, - ExecutedAmount: data.Delta.FillQuantity, - Exchange: b.Name, - OrderID: data.Delta.ID, - Type: orderType, - Side: orderSide, - Status: orderStatus, - AssetType: asset.Spot, - Date: data.Delta.CreatedAt, - Pair: pair, - } - return nil -} diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go deleted file mode 100644 index ea62bc2d..00000000 --- a/exchanges/bittrex/bittrex_wrapper.go +++ /dev/null @@ -1,1108 +0,0 @@ -package bittrex - -import ( - "context" - "errors" - "fmt" - "sort" - "strings" - "sync" - "time" - - "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" - "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" - "github.com/thrasher-corp/gocryptotrader/exchanges/account" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/deposit" - "github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate" - "github.com/thrasher-corp/gocryptotrader/exchanges/futures" - "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/protocol" - "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "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/trade" - "github.com/thrasher-corp/gocryptotrader/log" - "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" -) - -var ( - oneDay = time.Hour * 24 - oneMonth = oneDay * 31 - oneYear = oneDay * 366 -) - -// GetDefaultConfig returns a default exchange config -func (b *Bittrex) GetDefaultConfig(ctx context.Context) (*config.Exchange, error) { - b.SetDefaults() - exchCfg, err := b.GetStandardConfig() - if err != nil { - return nil, err - } - - err = b.SetupDefaults(exchCfg) - if err != nil { - return nil, err - } - - if b.Features.Supports.RESTCapabilities.AutoPairUpdates { - err = b.UpdateTradablePairs(ctx, true) - if err != nil { - return nil, err - } - } - return exchCfg, nil -} - -// SetDefaults sets the basic defaults for Bittrex -func (b *Bittrex) SetDefaults() { - b.Name = "Bittrex" - b.Enabled = true - b.Verbose = true - b.API.CredentialsValidator.RequiresKey = true - b.API.CredentialsValidator.RequiresSecret = true - - spot := currency.PairStore{ - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: currency.DashDelimiter, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: currency.DashDelimiter, - }, - } - - err := b.StoreAssetPairFormat(asset.Spot, spot) - if err != nil { - log.Errorln(log.ExchangeSys, err) - } - - b.Features = exchange.Features{ - Supports: exchange.FeaturesSupported{ - REST: true, - Websocket: true, - RESTCapabilities: protocol.Features{ - TickerFetching: true, - TickerBatching: true, - KlineFetching: true, - TradeFetching: true, - OrderbookFetching: true, - AutoPairUpdates: true, - GetOrder: true, - GetOrders: true, - CancelOrder: true, - CancelOrders: true, - SubmitOrder: true, - DepositHistory: true, - WithdrawalHistory: true, - UserTradeHistory: true, - CryptoDeposit: true, - CryptoWithdrawal: true, - TradeFee: true, - CryptoWithdrawalFee: true, - }, - WebsocketCapabilities: protocol.Features{ - TickerFetching: true, - OrderbookFetching: true, - Subscribe: true, - Unsubscribe: true, - }, - WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | - exchange.NoFiatWithdrawals, - }, - Enabled: exchange.FeaturesEnabled{ - AutoPairUpdates: true, - Kline: kline.ExchangeCapabilitiesEnabled{ - Intervals: kline.DeployExchangeIntervals( - kline.IntervalCapacity{Interval: kline.OneMin, Capacity: 1440}, // 1m interval: candles for 1 day (0:00 - 23:59) - kline.IntervalCapacity{Interval: kline.FiveMin, Capacity: 288}, // 5m interval: candles for 1 day (0:00 - 23:55) - kline.IntervalCapacity{Interval: kline.OneHour, Capacity: 744}, // 1 hour interval: candles for 31 days (0:00 - 23:00) - kline.IntervalCapacity{Interval: kline.OneDay, Capacity: 366}, // 1 day interval: candles for 366 days - ), - }, - }, - } - - b.Requester, err = request.New(b.Name, - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(request.NewBasicRateLimit(ratePeriod, rateLimit))) - if err != nil { - log.Errorln(log.ExchangeSys, err) - } - - b.API.Endpoints = b.NewEndpoints() - - err = b.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{ - exchange.RestSpot: bittrexAPIRestURL, - exchange.WebsocketSpot: bittrexAPIWSURL, - exchange.WebsocketSpotSupplementary: bittrexAPIWSNegotiationsURL, - }) - if err != nil { - log.Errorln(log.ExchangeSys, err) - } - b.Websocket = stream.New() - b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit - b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout - b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit -} - -// Setup takes in the supplied exchange configuration details and sets params -func (b *Bittrex) Setup(exch *config.Exchange) error { - err := exch.Validate() - if err != nil { - return err - } - if !exch.Enabled { - b.SetEnabled(false) - return nil - } - err = b.SetupDefaults(exch) - if err != nil { - return err - } - - wsRunningEndpoint, err := b.API.Endpoints.GetURL(exchange.WebsocketSpot) - if err != nil { - return err - } - - // Websocket details setup below - err = b.Websocket.Setup(&stream.WebsocketSetup{ - ExchangeConfig: exch, - DefaultURL: bittrexAPIWSURL, // Default ws endpoint so we can roll back via CLI if needed. - RunningURL: wsRunningEndpoint, - Connector: b.WsConnect, // Connector function outlined above. - Subscriber: b.Subscribe, // Subscriber function outlined above. - Unsubscriber: b.Unsubscribe, // Unsubscriber function outlined above. - GenerateSubscriptions: b.GenerateDefaultSubscriptions, // GenerateDefaultSubscriptions function outlined above. - Features: &b.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. - OrderbookBufferConfig: buffer.Config{ - SortBuffer: true, - SortBufferByUpdateIDs: true, - }, - }) - 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 b.Websocket.SetupNewConnection(stream.ConnectionSetup{ - ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, - ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - RateLimit: wsRateLimit, - // 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 - }) -} - -// GetServerTime returns the current exchange server time. -func (b *Bittrex) GetServerTime(_ context.Context, _ asset.Item) (time.Time, error) { - return time.Time{}, common.ErrFunctionNotSupported -} - -// Start starts the Bittrex go routine -func (b *Bittrex) Start(ctx context.Context, wg *sync.WaitGroup) error { - if wg == nil { - return fmt.Errorf("%T %w", wg, common.ErrNilPointer) - } - wg.Add(1) - go func() { - b.Run(ctx) - wg.Done() - }() - return nil -} - -// Run implements the Bittrex wrapper -func (b *Bittrex) Run(ctx context.Context) { - if b.Verbose { - log.Debugf(log.ExchangeSys, - "%s Websocket: %s.", - b.Name, - common.IsEnabled(b.Websocket.IsEnabled())) - b.PrintEnabledPairs() - } - - if !b.GetEnabledFeatures().AutoPairUpdates { - return - } - - err := b.UpdateTradablePairs(ctx, false) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to update tradable pairs. Err: %s", - b.Name, - err) - } - restURL, err := b.API.Endpoints.GetURL(exchange.RestSpot) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to check REST Spot URL. Err: %s", - b.Name, - err) - } - if restURL == bittrexAPIDeprecatedURL { - err = b.API.Endpoints.SetRunning(exchange.RestSpot.String(), bittrexAPIRestURL) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to update deprecated REST Spot URL. Err: %s", - b.Name, - err) - } - b.Config.API.Endpoints[exchange.RestSpot.String()] = bittrexAPIRestURL - log.Warnf(log.ExchangeSys, - "Deprecated %s REST URL updated from %s to %s", b.Name, bittrexAPIDeprecatedURL, bittrexAPIRestURL) - } -} - -// FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Bittrex) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.Pairs, error) { - // Bittrex only supports spot trading - if !b.SupportsAsset(a) { - return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a) - } - markets, err := b.GetMarkets(ctx) - if err != nil { - return nil, err - } - - pairs := make([]currency.Pair, 0, len(markets)) - for x := range markets { - if markets[x].Status != "ONLINE" { - continue - } - var pair currency.Pair - pair, err = currency.NewPairFromString(markets[x].Symbol) - if err != nil { - return nil, err - } - pairs = append(pairs, pair) - } - return pairs, nil -} - -// UpdateTradablePairs updates the exchanges available pairs and stores -// them in the exchanges config -func (b *Bittrex) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(ctx, asset.Spot) - if err != nil { - return err - } - err = b.UpdatePairs(pairs, asset.Spot, false, forceUpdate) - if err != nil { - return err - } - return b.EnsureOnePairEnabled() -} - -// UpdateTickers updates the ticker for all currency pairs of a given asset type -func (b *Bittrex) UpdateTickers(ctx context.Context, a asset.Item) error { - if a != asset.Spot { - return fmt.Errorf("%w %v", asset.ErrNotSupported, a) - } - tickers, err := b.GetTickers(ctx) - if err != nil { - return err - } - summaries, err := b.GetMarketSummaries(ctx) - if err != nil { - return err - } - for x := range tickers { - for y := range summaries { - if !strings.EqualFold(summaries[y].Symbol, tickers[x].Symbol) { - continue - } - var pair currency.Pair - pair, err = currency.NewPairFromString(tickers[x].Symbol) - if err != nil { - return err - } - tickerPrice := b.constructTicker(tickers[x], &summaries[y], pair, a) - err = ticker.ProcessTicker(tickerPrice) - if err != nil { - return err - } - } - } - return nil -} - -// UpdateTicker updates and returns the ticker for a currency pair -func (b *Bittrex) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) { - formattedPair, err := b.FormatExchangeCurrency(p, a) - if err != nil { - return nil, err - } - - t, err := b.GetTicker(ctx, formattedPair.String()) - if err != nil { - return nil, err - } - - s, err := b.GetMarketSummary(ctx, formattedPair.String()) - if err != nil { - return nil, err - } - - pair, err := currency.NewPairFromString(t.Symbol) - if err != nil { - return nil, err - } - - tickerPrice := b.constructTicker(t, &s, pair, a) - - err = ticker.ProcessTicker(tickerPrice) - if err != nil { - return nil, err - } - - return ticker.GetTicker(b.Name, p, a) -} - -// constructTicker constructs a ticker price from the underlying data -func (b *Bittrex) constructTicker(t TickerData, s *MarketSummaryData, pair currency.Pair, assetType asset.Item) *ticker.Price { - return &ticker.Price{ - Pair: pair, - Last: t.LastTradeRate, - Bid: t.BidRate, - Ask: t.AskRate, - High: s.High, - Low: s.Low, - Volume: s.Volume, - QuoteVolume: s.QuoteVolume, - LastUpdated: s.UpdatedAt, - AssetType: assetType, - ExchangeName: b.Name, - } -} - -// FetchTicker returns the ticker for a currency pair -func (b *Bittrex) FetchTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - resp, err := ticker.GetTicker(b.Name, p, assetType) - if err != nil { - return b.UpdateTicker(ctx, p, assetType) - } - return resp, nil -} - -// FetchOrderbook returns orderbook base on the currency pair -func (b *Bittrex) FetchOrderbook(ctx context.Context, c currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - resp, err := orderbook.Get(b.Name, c, assetType) - if err != nil { - return b.UpdateOrderbook(ctx, c, assetType) - } - return resp, nil -} - -// UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bittrex) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - if p.IsEmpty() { - return nil, currency.ErrCurrencyPairEmpty - } - if err := b.CurrencyPairs.IsAssetEnabled(assetType); err != nil { - return nil, err - } - book := &orderbook.Base{ - Exchange: b.Name, - Pair: p, - Asset: assetType, - VerifyOrderbook: b.CanVerifyOrderbook, - } - - formattedPair, err := b.FormatExchangeCurrency(p, assetType) - if err != nil { - return book, err - } - - // Valid order book depths are 1, 25 and 500 - orderbookData, sequence, err := b.GetOrderbook(ctx, formattedPair.String(), orderbookDepth) - if err != nil { - return book, err - } - - book.LastUpdateID = sequence - book.Bids = make(orderbook.Items, len(orderbookData.Bid)) - book.Asks = make(orderbook.Items, len(orderbookData.Ask)) - - for x := range orderbookData.Bid { - book.Bids[x] = orderbook.Item{ - Amount: orderbookData.Bid[x].Quantity, - Price: orderbookData.Bid[x].Rate, - } - } - - for x := range orderbookData.Ask { - book.Asks[x] = orderbook.Item{ - Amount: orderbookData.Ask[x].Quantity, - Price: orderbookData.Ask[x].Rate, - } - } - err = book.Process() - if err != nil { - return book, err - } - - return orderbook.Get(b.Name, p, assetType) -} - -// UpdateAccountInfo retrieves balances for all enabled currencies -func (b *Bittrex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) { - var resp account.Holdings - balanceData, err := b.GetBalances(ctx) - if err != nil { - return resp, err - } - - currencies := make([]account.Balance, len(balanceData)) - for i := range balanceData { - currencies[i] = account.Balance{ - Currency: currency.NewCode(balanceData[i].CurrencySymbol), - Total: balanceData[i].Total, - Hold: balanceData[i].Total - balanceData[i].Available, - Free: balanceData[i].Available, - } - } - - resp.Accounts = append(resp.Accounts, account.SubAccount{ - AssetType: assetType, - Currencies: currencies, - }) - resp.Exchange = b.Name - - creds, err := b.GetCredentials(ctx) - if err != nil { - return account.Holdings{}, err - } - return resp, account.Process(&resp, creds) -} - -// FetchAccountInfo retrieves balances for all enabled currencies -func (b *Bittrex) FetchAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) { - creds, err := b.GetCredentials(ctx) - if err != nil { - return account.Holdings{}, err - } - resp, err := account.GetHoldings(b.Name, creds, assetType) - if err != nil { - return b.UpdateAccountInfo(ctx, assetType) - } - return resp, nil -} - -// GetAccountFundingHistory returns funding history, deposits and -// withdrawals -func (b *Bittrex) GetAccountFundingHistory(ctx context.Context) ([]exchange.FundingHistory, error) { - closedDepositData, err := b.GetClosedDeposits(ctx) - if err != nil { - return nil, err - } - openDepositData, err := b.GetOpenDeposits(ctx) - if err != nil { - return nil, err - } - closedWithdrawalData, err := b.GetClosedWithdrawals(ctx) - if err != nil { - return nil, err - } - openWithdrawalData, err := b.GetOpenWithdrawals(ctx) - if err != nil { - return nil, err - } - - depositData := make([]DepositData, 0, len(closedDepositData)+len(openDepositData)) - depositData = append(depositData, closedDepositData...) - depositData = append(depositData, openDepositData...) - - withdrawalData := make([]WithdrawalData, 0, len(closedWithdrawalData)+len(openWithdrawalData)) - withdrawalData = append(withdrawalData, closedWithdrawalData...) - withdrawalData = append(withdrawalData, openWithdrawalData...) - - resp := make([]exchange.FundingHistory, 0, len(depositData)+len(withdrawalData)) - for x := range depositData { - resp = append(resp, exchange.FundingHistory{ - ExchangeName: b.Name, - Status: depositData[x].Status, - Description: depositData[x].CryptoAddressTag, - Timestamp: depositData[x].UpdatedAt, - Currency: depositData[x].CurrencySymbol, - Amount: depositData[x].Quantity, - TransferType: "deposit", - CryptoToAddress: depositData[x].CryptoAddress, - CryptoTxID: depositData[x].TxID, - }) - } - for x := range withdrawalData { - resp = append(resp, exchange.FundingHistory{ - ExchangeName: b.Name, - Status: withdrawalData[x].Status, - Description: withdrawalData[x].CryptoAddressTag, - Timestamp: depositData[x].UpdatedAt, - Currency: withdrawalData[x].CurrencySymbol, - Amount: withdrawalData[x].Quantity, - Fee: withdrawalData[x].TxCost, - TransferType: "withdrawal", - CryptoToAddress: withdrawalData[x].CryptoAddress, - CryptoTxID: withdrawalData[x].TxID, - TransferID: withdrawalData[x].ID, - }) - } - return resp, nil -} - -// GetWithdrawalsHistory returns previous withdrawals data -func (b *Bittrex) GetWithdrawalsHistory(_ context.Context, _ currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) { - return nil, common.ErrFunctionNotSupported -} - -// GetRecentTrades returns the most recent trades for a currency and asset -func (b *Bittrex) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { - formattedPair, err := b.FormatExchangeCurrency(p, assetType) - if err != nil { - return nil, err - } - - tradeData, err := b.GetMarketHistory(ctx, formattedPair.String()) - if err != nil { - return nil, err - } - - resp := make([]trade.Data, len(tradeData)) - for i := range tradeData { - var side order.Side - side, err = order.StringToOrderSide(tradeData[i].TakerSide) - if err != nil { - return nil, err - } - resp[i] = trade.Data{ - Exchange: b.Name, - TID: tradeData[i].ID, - CurrencyPair: formattedPair, - AssetType: assetType, - Side: side, - Price: tradeData[i].Rate, - Amount: tradeData[i].Quantity, - Timestamp: tradeData[i].ExecutedAt, - } - } - - err = b.AddTradesToBuffer(resp...) - if err != nil { - return nil, err - } - - sort.Sort(trade.ByDate(resp)) - return resp, nil -} - -// GetHistoricTrades returns historic trade data within the timeframe provided -// Bittrex only reports recent trades -func (b *Bittrex) GetHistoricTrades(_ context.Context, _ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) { - return nil, common.ErrFunctionNotSupported -} - -// SubmitOrder submits a new order -func (b *Bittrex) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) { - if err := s.Validate(); err != nil { - return nil, err - } - - if s.Side.IsShort() { - s.Side = order.Sell - } - - if s.Side.IsLong() { - s.Side = order.Buy - } - - formattedPair, err := b.FormatExchangeCurrency(s.Pair, s.AssetType) - if err != nil { - return nil, err - } - - orderData, err := b.Order(ctx, - formattedPair.String(), - s.Side.String(), - s.Type.String(), - GoodTilCancelled, - s.Price, - s.Amount, - 0.0) - if err != nil { - return nil, err - } - return s.DeriveSubmitResponse(orderData.ID) -} - -// ModifyOrder will allow of changing orderbook placement and limit to -// market conversion -func (b *Bittrex) ModifyOrder(_ context.Context, _ *order.Modify) (*order.ModifyResponse, error) { - return nil, common.ErrFunctionNotSupported -} - -// CancelOrder cancels an order by its corresponding ID number -func (b *Bittrex) CancelOrder(ctx context.Context, ord *order.Cancel) error { - if err := ord.Validate(ord.StandardCancel()); err != nil { - return err - } - _, err := b.CancelExistingOrder(ctx, ord.OrderID) - return err -} - -// CancelBatchOrders cancels an orders by their corresponding ID numbers -func (b *Bittrex) CancelBatchOrders(_ context.Context, _ []order.Cancel) (*order.CancelBatchResponse, error) { - return nil, common.ErrFunctionNotSupported -} - -// CancelAllOrders cancels all orders associated with a currency pair, or cancels all orders for all -// pairs if no pair was specified -func (b *Bittrex) CancelAllOrders(ctx context.Context, orderCancellation *order.Cancel) (order.CancelAllResponse, error) { - var pair string - if orderCancellation != nil { - formattedPair, err := b.FormatExchangeCurrency(orderCancellation.Pair, orderCancellation.AssetType) - if err != nil { - return order.CancelAllResponse{}, err - } - pair = formattedPair.String() - } - orderData, err := b.CancelOpenOrders(ctx, pair) - if err != nil { - return order.CancelAllResponse{}, err - } - - tempMap := make(map[string]string) - for x := range orderData { - if orderData[x].Result.Status == "CLOSED" { - tempMap[orderData[x].ID] = "Success" - } - } - resp := order.CancelAllResponse{ - Status: tempMap, - Count: int64(len(tempMap)), - } - return resp, nil -} - -// GetOrderInfo returns information on a current open order -func (b *Bittrex) GetOrderInfo(ctx context.Context, orderID string, _ currency.Pair, _ asset.Item) (*order.Detail, error) { - orderData, err := b.GetOrder(ctx, orderID) - if err != nil { - return nil, err - } - - return b.ConstructOrderDetail(&orderData) -} - -// ConstructOrderDetail constructs an order detail item from the underlying data -func (b *Bittrex) ConstructOrderDetail(orderData *OrderData) (*order.Detail, error) { - immediateOrCancel := false - if orderData.TimeInForce == string(ImmediateOrCancel) { - immediateOrCancel = true - } - - format, err := b.GetPairFormat(asset.Spot, false) - if err != nil { - return nil, err - } - orderPair, err := currency.NewPairDelimiter(orderData.MarketSymbol, - format.Delimiter) - if err != nil { - log.Errorf(log.ExchangeSys, - "Exchange %v Func %v Order %v Could not parse currency pair %v", - b.Name, - "GetActiveOrders", - orderData.ID, - err) - } - orderType, err := order.StringToOrderType(orderData.Type) - if err != nil { - log.Errorf(log.ExchangeSys, "%s %v", b.Name, err) - } - - var orderStatus order.Status - switch orderData.Status { - case order.Open.String(): - switch orderData.FillQuantity { - case 0: - orderStatus = order.Open - default: - orderStatus = order.PartiallyFilled - } - case order.Closed.String(): - switch orderData.FillQuantity { - case 0: - orderStatus = order.Cancelled - case orderData.Quantity: - orderStatus = order.Filled - default: - orderStatus = order.PartiallyCancelled - } - } - - return &order.Detail{ - ImmediateOrCancel: immediateOrCancel, - Amount: orderData.Quantity, - ExecutedAmount: orderData.FillQuantity, - RemainingAmount: orderData.Quantity - orderData.FillQuantity, - Price: orderData.Limit, - Date: orderData.CreatedAt, - OrderID: orderData.ID, - Exchange: b.Name, - Type: orderType, - Pair: orderPair, - Status: orderStatus, - }, nil -} - -// GetDepositAddress returns a deposit address for a specified currency -func (b *Bittrex) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, _ string) (*deposit.Address, error) { - depositAddr, err := b.GetCryptoDepositAddress(ctx, cryptocurrency.String()) - if err != nil { - return nil, err - } - - return &deposit.Address{ - Address: depositAddr.CryptoAddress, - Tag: depositAddr.CryptoAddressTag, - }, nil -} - -// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is -// submitted -func (b *Bittrex) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { - if err := withdrawRequest.Validate(); err != nil { - return nil, err - } - result, err := b.Withdraw(ctx, - withdrawRequest.Currency.String(), - withdrawRequest.Crypto.AddressTag, - withdrawRequest.Crypto.Address, - withdrawRequest.Amount) - if err != nil { - return nil, err - } - return &withdraw.ExchangeResponse{ - Name: b.Name, - ID: result.ID, - Status: result.Status, - }, err -} - -// WithdrawFiatFunds returns a withdrawal ID when a -// withdrawal is submitted -func (b *Bittrex) WithdrawFiatFunds(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) { - return nil, common.ErrFunctionNotSupported -} - -// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a -// withdrawal is submitted -func (b *Bittrex) WithdrawFiatFundsToInternationalBank(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) { - return nil, common.ErrFunctionNotSupported -} - -// GetActiveOrders retrieves any orders that are active/open -func (b *Bittrex) GetActiveOrders(ctx context.Context, req *order.MultiOrderRequest) (order.FilteredOrders, error) { - err := req.Validate() - if err != nil { - return nil, err - } - - var currPair string - if len(req.Pairs) == 1 { - var formattedPair currency.Pair - formattedPair, err = b.FormatExchangeCurrency(req.Pairs[0], asset.Spot) - if err != nil { - return nil, err - } - currPair = formattedPair.String() - } - - format, err := b.GetPairFormat(asset.Spot, false) - if err != nil { - return nil, err - } - - orderData, sequence, err := b.GetOpenOrders(ctx, currPair) - if err != nil { - return nil, err - } - - resp := make([]order.Detail, 0, len(orderData)) - for i := range orderData { - var pair currency.Pair - pair, err = currency.NewPairDelimiter(orderData[i].MarketSymbol, - format.Delimiter) - if err != nil { - log.Errorf(log.ExchangeSys, - "Exchange %v Func %v Order %v Could not parse currency pair %v", - b.Name, - "GetActiveOrders", - orderData[i].ID, - err) - } - - var orderType order.Type - orderType, err = order.StringToOrderType(orderData[i].Type) - if err != nil { - log.Errorf(log.ExchangeSys, "%s %v", b.Name, err) - } - - var orderSide order.Side - orderSide, err = order.StringToOrderSide(orderData[i].Direction) - if err != nil { - log.Errorf(log.ExchangeSys, "GetActiveOrders - %s - cannot get order side - %s\n", b.Name, err.Error()) - } - - resp = append(resp, order.Detail{ - Amount: orderData[i].Quantity, - RemainingAmount: orderData[i].Quantity - orderData[i].FillQuantity, - ExecutedAmount: orderData[i].FillQuantity, - Price: orderData[i].Limit, - Date: orderData[i].CreatedAt, - OrderID: orderData[i].ID, - Exchange: b.Name, - Type: orderType, - Side: orderSide, - Status: order.Active, - Pair: pair, - }) - } - b.WsSequenceOrders = sequence - return req.Filter(b.Name, resp), nil -} - -// GetOrderHistory retrieves account order information -// Can Limit response to specific order status -func (b *Bittrex) GetOrderHistory(ctx context.Context, req *order.MultiOrderRequest) (order.FilteredOrders, error) { - err := req.Validate() - if err != nil { - return nil, err - } - if len(req.Pairs) == 0 { - return nil, errors.New("at least one currency is required to fetch order history") - } - - format, err := b.GetPairFormat(asset.Spot, false) - if err != nil { - return nil, err - } - - var resp []order.Detail - for x := range req.Pairs { - var formattedPair currency.Pair - formattedPair, err = b.FormatExchangeCurrency(req.Pairs[x], req.AssetType) - if err != nil { - return nil, err - } - - var orderData []OrderData - orderData, err = b.GetOrderHistoryForCurrency(ctx, formattedPair.String()) - if err != nil { - return nil, err - } - - for i := range orderData { - var pair currency.Pair - pair, err = currency.NewPairDelimiter(orderData[i].MarketSymbol, - format.Delimiter) - if err != nil { - log.Errorf(log.ExchangeSys, - "Exchange %v Func %v Order %v Could not parse currency pair %v", - b.Name, - "GetOrderHistory", - orderData[i].ID, - err) - } - var orderType order.Type - orderType, err = order.StringToOrderType(orderData[i].Type) - if err != nil { - log.Errorf(log.ExchangeSys, "%s %v", b.Name, err) - } - - var orderSide order.Side - orderSide, err = order.StringToOrderSide(orderData[i].Direction) - if err != nil { - log.Errorf(log.ExchangeSys, "GetActiveOrders - %s - cannot get order side - %s\n", b.Name, err.Error()) - } - - var orderStatus order.Status - orderStatus, err = order.StringToOrderStatus(orderData[i].Status) - if err != nil { - log.Errorf(log.ExchangeSys, "GetActiveOrders - %s - cannot get order status - %s\n", b.Name, err.Error()) - } - - detail := order.Detail{ - Amount: orderData[i].Quantity, - ExecutedAmount: orderData[i].FillQuantity, - RemainingAmount: orderData[i].Quantity - orderData[i].FillQuantity, - Price: orderData[i].Limit, - Date: orderData[i].CreatedAt, - CloseTime: orderData[i].ClosedAt, - OrderID: orderData[i].ID, - Exchange: b.Name, - Type: orderType, - Side: orderSide, - Status: orderStatus, - Fee: orderData[i].Commission, - Pair: pair, - } - detail.InferCostsAndTimes() - resp = append(resp, detail) - } - } - return req.Filter(b.Name, resp), nil -} - -// GetFeeByType returns an estimate of fee based on type of transaction -func (b *Bittrex) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { - if feeBuilder == nil { - return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) - } - if !b.AreCredentialsValid(ctx) && // Todo check connection status - feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { - feeBuilder.FeeType = exchange.OfflineTradeFee - } - return b.GetFee(ctx, feeBuilder) -} - -// ValidateAPICredentials validates current credentials used for wrapper -// functionality -func (b *Bittrex) ValidateAPICredentials(ctx context.Context, assetType asset.Item) error { - _, err := b.UpdateAccountInfo(ctx, assetType) - return b.CheckTransientError(err) -} - -// FormatExchangeKlineInterval returns Interval to string -// Overrides Base function -func (b *Bittrex) FormatExchangeKlineInterval(in kline.Interval) string { - switch in { - case kline.OneMin: - return "MINUTE_1" - case kline.FiveMin: - return "MINUTE_5" - case kline.OneHour: - return "HOUR_1" - case kline.OneDay: - return "DAY_1" - default: - return "notfound" - } -} - -// GetHistoricCandles returns candles between a time period for a set time interval -// Candles set size returned by Bittrex depends on interval length: -// - 1m interval: candles for 1 day (0:00 - 23:59) -// - 5m interval: candles for 1 day (0:00 - 23:55) -// - 1 hour interval: candles for 31 days -// - 1 day interval: candles for 366 days -// This implementation rounds returns candles up to the next interval or to the end -// time (whichever comes first) -func (b *Bittrex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) { - req, err := b.GetKlineRequest(pair, a, interval, start, end, false) - if err != nil { - return nil, err - } - - candleInterval := b.FormatExchangeKlineInterval(req.ExchangeInterval) - if candleInterval == "notfound" { - return nil, fmt.Errorf("%w %v", kline.ErrInvalidInterval, interval) - } - - year, month, day := req.End.Date() - curYear, curMonth, curDay := time.Now().Date() - var getHistoric, getRecent bool - switch req.ExchangeInterval { - case kline.OneMin, kline.FiveMin: - if time.Since(req.Start) > oneDay { - getHistoric = true - } - if year >= curYear && month >= curMonth && day >= curDay { - getRecent = true - } - case kline.OneHour: - if time.Since(req.Start) > oneMonth { - getHistoric = true - } - if year >= curYear && month >= curMonth { - getRecent = true - } - case kline.OneDay: - if time.Since(req.Start) > oneYear { - getHistoric = true - } - if year >= curYear { - getRecent = true - } - } - - if !getHistoric && !getRecent { - return nil, errors.New("start end time range cannot get historic or recent candles") - } - - var candleData []CandleData - if getHistoric { - historicData, err := b.GetHistoricalCandles(ctx, - req.RequestFormatted.String(), - candleInterval, - "TRADE", - start.Year(), - int(start.Month()), - start.Day()) - if err != nil { - return nil, err - } - candleData = append(candleData, historicData...) - } - - if getRecent { - recentData, err := b.GetRecentCandles(ctx, - req.RequestFormatted.String(), - candleInterval, - "TRADE") - if err != nil { - return nil, err - } - candleData = append(candleData, recentData...) - } - - timeSeries := make([]kline.Candle, 0, len(candleData)) - for x := range candleData { - if candleData[x].StartsAt.Before(req.Start) || candleData[x].StartsAt.After(req.End) { - continue - } - timeSeries = append(timeSeries, kline.Candle{ - Time: candleData[x].StartsAt, - Open: candleData[x].Open, - High: candleData[x].High, - Low: candleData[x].Low, - Close: candleData[x].Close, - Volume: candleData[x].Volume, - }) - } - return req.ProcessResponse(timeSeries) -} - -// GetHistoricCandlesExtended returns candles between a time period for a set time interval -func (b *Bittrex) GetHistoricCandlesExtended(_ context.Context, _ currency.Pair, _ asset.Item, _ kline.Interval, _, _ time.Time) (*kline.Item, error) { - // TODO implement with API upgrade˜ - return nil, common.ErrFunctionNotSupported -} - -// GetFuturesContractDetails returns all contracts from the exchange by asset type -func (b *Bittrex) GetFuturesContractDetails(context.Context, asset.Item) ([]futures.Contract, error) { - return nil, common.ErrFunctionNotSupported -} - -// GetLatestFundingRates returns the latest funding rates data -func (b *Bittrex) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) { - return nil, common.ErrFunctionNotSupported -} - -// UpdateOrderExecutionLimits updates order execution limits -func (b *Bittrex) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error { - return common.ErrNotYetImplemented -} diff --git a/exchanges/bittrex/bittrex_ws_orderbook.go b/exchanges/bittrex/bittrex_ws_orderbook.go deleted file mode 100644 index 508ce092..00000000 --- a/exchanges/bittrex/bittrex_ws_orderbook.go +++ /dev/null @@ -1,437 +0,0 @@ -package bittrex - -import ( - "context" - "fmt" - - "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-corp/gocryptotrader/log" -) - -var ( - // maxWSUpdateBuffer defines max websocket updates to apply when an - // orderbook is initially fetched - maxWSUpdateBuffer = 150 - // maxWSOrderbookJobs defines max websocket orderbook jobs in queue to fetch - // an orderbook snapshot via REST - maxWSOrderbookJobs = 2000 - // maxWSOrderbookWorkers defines a max amount of workers allowed to execute - // jobs from the job channel - maxWSOrderbookWorkers = 10 -) - -func (b *Bittrex) setupOrderbookManager() { - if b.obm == nil { - b.obm = &orderbookManager{ - state: make(map[currency.Code]map[currency.Code]map[asset.Item]*update), - jobs: make(chan job, maxWSOrderbookJobs), - } - } else { - // Change state on reconnect for initial sync. - for _, m1 := range b.obm.state { - for _, m2 := range m1 { - for _, update := range m2 { - update.initialSync = true - update.needsFetchingBook = true - } - } - } - } - - for i := 0; i < maxWSOrderbookWorkers; i++ { - // 10 workers for synchronising book - b.SynchroniseWebsocketOrderbook() - } -} - -// ProcessUpdateOB processes the websocket orderbook update -func (b *Bittrex) ProcessUpdateOB(pair currency.Pair, message *OrderbookUpdateMessage) error { - updateBids := make([]orderbook.Item, len(message.BidDeltas)) - for x := range message.BidDeltas { - updateBids[x] = orderbook.Item{ - Price: message.BidDeltas[x].Rate, - Amount: message.BidDeltas[x].Quantity, - } - } - updateAsks := make([]orderbook.Item, len(message.AskDeltas)) - for x := range message.AskDeltas { - updateAsks[x] = orderbook.Item{ - Price: message.AskDeltas[x].Rate, - Amount: message.AskDeltas[x].Quantity, - } - } - - return b.Websocket.Orderbook.Update(&orderbook.Update{ - Asset: asset.Spot, - Pair: pair, - UpdateID: message.Sequence, - Bids: updateBids, - Asks: updateAsks, - }) -} - -// UpdateLocalOBBuffer updates and returns the most recent iteration of the orderbook -func (b *Bittrex) UpdateLocalOBBuffer(update *OrderbookUpdateMessage) (bool, error) { - enabledPairs, err := b.GetEnabledPairs(asset.Spot) - if err != nil { - return false, err - } - - format, err := b.GetPairFormat(asset.Spot, true) - if err != nil { - return false, err - } - - currencyPair, err := currency.NewPairFromFormattedPairs(update.MarketSymbol, - enabledPairs, - format) - if err != nil { - return false, err - } - - err = b.obm.stageWsUpdate(update, currencyPair, asset.Spot) - if err != nil { - init, err2 := b.obm.checkIsInitialSync(currencyPair) - if err2 != nil { - return false, err2 - } - return init, err - } - - err = b.applyBufferUpdate(currencyPair) - if err != nil { - log.Errorf(log.WebsocketMgr, "%s websocket UpdateLocalOBBuffer: Could not apply buffer update\n", b.Name) - } - - return false, err -} - -// SeedLocalOBCache seeds depth data -func (b *Bittrex) SeedLocalOBCache(ctx context.Context, p currency.Pair) error { - ob, sequence, err := b.GetOrderbook(ctx, p.String(), orderbookDepth) - if err != nil { - return err - } - return b.SeedLocalCacheWithOrderBook(p, sequence, ob, orderbookDepth) -} - -// SeedLocalCacheWithOrderBook seeds the local orderbook cache -func (b *Bittrex) SeedLocalCacheWithOrderBook(p currency.Pair, sequence int64, orderbookNew *OrderbookData, maxDepth int) error { - newOrderBook := orderbook.Base{ - Pair: p, - Asset: asset.Spot, - Exchange: b.Name, - LastUpdateID: sequence, - VerifyOrderbook: b.CanVerifyOrderbook, - Bids: make(orderbook.Items, len(orderbookNew.Bid)), - Asks: make(orderbook.Items, len(orderbookNew.Ask)), - MaxDepth: maxDepth, - } - - for i := range orderbookNew.Bid { - newOrderBook.Bids[i] = orderbook.Item{ - Amount: orderbookNew.Bid[i].Quantity, - Price: orderbookNew.Bid[i].Rate, - } - } - for i := range orderbookNew.Ask { - newOrderBook.Asks[i] = orderbook.Item{ - Amount: orderbookNew.Ask[i].Quantity, - Price: orderbookNew.Ask[i].Rate, - } - } - - return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) -} - -// applyBufferUpdate applies the buffer to the orderbook or initiates a new -// orderbook sync by the REST protocol which is off handed to go routine. -func (b *Bittrex) applyBufferUpdate(pair currency.Pair) error { - fetching, needsFetching, err := b.obm.handleFetchingBook(pair) - if err != nil { - return err - } - if fetching { - return nil - } - if needsFetching { - if b.Verbose { - log.Debugf(log.WebsocketMgr, "%s Orderbook: Fetching via REST\n", b.Name) - } - return b.obm.fetchBookViaREST(pair) - } - recent, err := b.Websocket.Orderbook.GetOrderbook(pair, asset.Spot) - if err != nil { - log.Errorf( - log.WebsocketMgr, - "%s error fetching recent orderbook when applying updates: %s\n", - b.Name, - err) - } - - if recent != nil { - err = b.obm.checkAndProcessUpdate(b.ProcessUpdateOB, pair, recent) - if err != nil { - log.Errorf( - log.WebsocketMgr, - "%s error processing update - initiating new orderbook sync via REST: %s\n", - b.Name, - err) - err = b.obm.setNeedsFetchingBook(pair) - if err != nil { - return err - } - } - } - return nil -} - -// SynchroniseWebsocketOrderbook synchronises full orderbook for currency pair -// asset -func (b *Bittrex) SynchroniseWebsocketOrderbook() { - b.Websocket.Wg.Add(1) - go func() { - defer b.Websocket.Wg.Done() - for { - select { - case <-b.Websocket.ShutdownC: - for { - select { - case <-b.obm.jobs: - default: - return - } - } - case j := <-b.obm.jobs: - err := b.processJob(j.Pair) - if err != nil { - log.Errorf(log.WebsocketMgr, - "%s processing websocket orderbook error %v", - b.Name, err) - } - } - } - }() -} - -// processJob fetches and processes orderbook updates -func (b *Bittrex) processJob(p currency.Pair) error { - err := b.SeedLocalOBCache(context.TODO(), p) - if err != nil { - return fmt.Errorf("%s %s seeding local cache for orderbook error: %v", - p, asset.Spot, err) - } - - err = b.obm.stopFetchingBook(p) - if err != nil { - return err - } - - // Immediately apply the buffer updates so we don't wait for a - // new update to initiate this. - return b.applyBufferUpdate(p) -} - -// stageWsUpdate stages websocket update to roll through updates that need to -// be applied to a fetched orderbook via REST. -func (o *orderbookManager) stageWsUpdate(u *OrderbookUpdateMessage, pair currency.Pair, a asset.Item) error { - o.Lock() - defer o.Unlock() - m1, ok := o.state[pair.Base] - if !ok { - m1 = make(map[currency.Code]map[asset.Item]*update) - o.state[pair.Base] = m1 - } - - m2, ok := m1[pair.Quote] - if !ok { - m2 = make(map[asset.Item]*update) - m1[pair.Quote] = m2 - } - - state, ok := m2[a] - if !ok { - state = &update{ - // 100ms update assuming we might have up to a 10 second delay. - // There could be a potential 100 updates for the currency. - buffer: make(chan *OrderbookUpdateMessage, maxWSUpdateBuffer), - fetchingBook: false, - initialSync: true, - needsFetchingBook: true, - } - m2[a] = state - } - - select { - // Put update in the channel buffer to be processed - case state.buffer <- u: - return nil - default: - <-state.buffer // pop one element - state.buffer <- u // to shift buffer on fail - return fmt.Errorf("channel blockage for %s, asset %s and connection", - pair, a) - } -} - -// stopFetchingBook completes the book fetching. -func (o *orderbookManager) stopFetchingBook(pair currency.Pair) error { - o.Lock() - defer o.Unlock() - state, ok := o.state[pair.Base][pair.Quote][asset.Spot] - if !ok { - return fmt.Errorf("could not match pair %s and asset type %s in hash table", - pair, - asset.Spot) - } - if !state.fetchingBook { - return fmt.Errorf("fetching book already set to false for %s %s", - pair, - asset.Spot) - } - state.fetchingBook = false - return nil -} - -// setNeedsFetchingBook completes the book fetching initiation. -func (o *orderbookManager) setNeedsFetchingBook(pair currency.Pair) error { - o.Lock() - defer o.Unlock() - state, ok := o.state[pair.Base][pair.Quote][asset.Spot] - if !ok { - return fmt.Errorf("could not match pair %s and asset type %s in hash table", - pair, - asset.Spot) - } - state.needsFetchingBook = true - return nil -} - -// handleFetchingBook checks if a full book is being fetched or needs to be -// fetched -func (o *orderbookManager) handleFetchingBook(pair currency.Pair) (fetching, needsFetching bool, err error) { - o.Lock() - defer o.Unlock() - state, ok := o.state[pair.Base][pair.Quote][asset.Spot] - if !ok { - return false, false, - fmt.Errorf("check is fetching book cannot match currency pair %s asset type %s", - pair, - asset.Spot) - } - - if state.fetchingBook { - return true, false, nil - } - - if state.needsFetchingBook { - state.needsFetchingBook = false - state.fetchingBook = true - return false, true, nil - } - return false, false, nil -} - -// checkIsInitialSync checks status if the book is Initial Sync being via the REST -// protocol. -func (o *orderbookManager) checkIsInitialSync(pair currency.Pair) (bool, error) { - o.Lock() - defer o.Unlock() - state, ok := o.state[pair.Base][pair.Quote][asset.Spot] - if !ok { - return false, - fmt.Errorf("checkIsInitialSync of orderbook cannot match currency pair %s asset type %s", - pair, - asset.Spot) - } - return state.initialSync, nil -} - -// fetchBookViaREST pushes a job of fetching the orderbook via the REST protocol -// to get an initial full book that we can apply our buffered updates too. -func (o *orderbookManager) fetchBookViaREST(pair currency.Pair) error { - o.Lock() - defer o.Unlock() - - state, ok := o.state[pair.Base][pair.Quote][asset.Spot] - if !ok { - return fmt.Errorf("fetch book via rest cannot match currency pair %s asset type %s", - pair, - asset.Spot) - } - - state.initialSync = true - state.fetchingBook = true - - select { - case o.jobs <- job{pair}: - return nil - default: - return fmt.Errorf("%s %s book synchronisation channel blocked up", - pair, - asset.Spot) - } -} - -func (o *orderbookManager) checkAndProcessUpdate(processor func(currency.Pair, *OrderbookUpdateMessage) error, pair currency.Pair, recent *orderbook.Base) error { - o.Lock() - defer o.Unlock() - state, ok := o.state[pair.Base][pair.Quote][asset.Spot] - if !ok { - return fmt.Errorf("could not match pair [%s] asset type [%s] in hash table to process websocket orderbook update", - pair, asset.Spot) - } - - // This will continuously remove updates from the buffered channel and - // apply them to the current orderbook. -buffer: - for { - select { - case d := <-state.buffer: - process, err := state.validate(d, recent) - if err != nil { - return err - } - if process { - err := processor(pair, d) - if err != nil { - return fmt.Errorf("%s %s processing update error: %w", - pair, asset.Spot, err) - } - recent.LastUpdateID = d.Sequence - } - default: - break buffer - } - } - return nil -} - -// validate checks for correct update alignment -func (u *update) validate(updt *OrderbookUpdateMessage, recent *orderbook.Base) (bool, error) { - if updt.Sequence <= recent.LastUpdateID { - // Drop any event where u is <= lastUpdateId in the snapshot. - return false, nil - } - - id := recent.LastUpdateID + 1 - if u.initialSync { - // The first processed event should have U <= lastUpdateId+1 AND - // u >= lastUpdateId+1. - if updt.Sequence > id { - return false, fmt.Errorf("initial websocket orderbook sync failure for pair %s and asset %s", - recent.Pair, - asset.Spot) - } - u.initialSync = false - } else if updt.Sequence != id { - // While listening to the stream, each new event's U should be - // equal to the previous event's u+1. - return false, fmt.Errorf("websocket orderbook synchronisation failure for pair %s and asset %s", - recent.Pair, - asset.Spot) - } - return true, nil -} diff --git a/exchanges/kucoin/kucoin_test.go b/exchanges/kucoin/kucoin_test.go index e954fefc..8be7279d 100644 --- a/exchanges/kucoin/kucoin_test.go +++ b/exchanges/kucoin/kucoin_test.go @@ -73,6 +73,7 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal(err) } + request.MaxRequestJobs = 100 ku.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() ku.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() @@ -2480,11 +2481,11 @@ func TestProcessMarketSnapshot(t *testing.T) { assert.Equal(t, 0.00000039450000000000, v.High, "high") assert.Equal(t, 0.0000003897, v.Last, "lastTradedPrice") assert.Equal(t, 0.00000034200000000000, v.Low, "low") - assert.Equal(t, currency.NewPairWithDelimiter("MTV", "BTC", "-"), v.Pair, "symbol") + assert.Equal(t, currency.NewPairWithDelimiter("ETH", "BTC", "-"), v.Pair, "symbol") assert.Equal(t, 316078.69700000000000000000, v.Volume, "volume") assert.Equal(t, 0.11768519138877000000, v.QuoteVolume, "volValue") // both margin and spot - case 3: + case 3, 4: assert.Equal(t, time.UnixMilli(1698740324437), v.LastUpdated, "datetime") assert.Equal(t, 0.00008486000000000000, v.High, "high") assert.Equal(t, 0.00008318, v.Last, "lastTradedPrice") @@ -2505,7 +2506,6 @@ func TestProcessMarketSnapshot(t *testing.T) { func TestSubscribeMarketSnapshot(t *testing.T) { t.Parallel() - setupWS() s := []stream.ChannelSubscription{ {Channel: marketTickerSnapshotForCurrencyChannel, Currency: currency.Pair{Base: currency.BTC}}, diff --git a/exchanges/kucoin/testdata/wsMarketSnapshot.json b/exchanges/kucoin/testdata/wsMarketSnapshot.json index 51261e57..a4ab30e4 100644 --- a/exchanges/kucoin/testdata/wsMarketSnapshot.json +++ b/exchanges/kucoin/testdata/wsMarketSnapshot.json @@ -1,3 +1,3 @@ {"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":1698740324504,"data":{"averagePrice":0.00001164,"baseCurrency":"XMR","board":0,"buy":0.00001252,"changePrice":0.00000104800000000000,"changeRate":0.0914,"close":0.000012508,"datetime":1698740324415,"high":0.00001402100000000000,"lastTradedPrice":0.000012508,"low":0.00001129200000000000,"makerCoefficient":2.000000,"makerFeeRate":0.001,"marginTrade":false,"mark":0,"market":"BTC","marketChange1h":{"changeRate":0,"high":0,"low":0,"open":0,"vol":0,"volValue":0},"marketChange24h":{"changePrice":0.00000104800000000000,"changeRate":0.0914,"high":0.00001402100000000000,"low":0.00001129200000000000,"open":0.00001146000000000000,"vol":28474.47280000000000000000,"volValue":0.37038038297340000000},"marketChange4h":{"changePrice":0.00000009600000000000,"changeRate":0.0077,"high":0.00001308400000000000,"low":0.00001241200000000000,"open":0.00001241200000000000,"vol":7090.00000000000000000000,"volValue":0.08885800028840000000},"markets":["BTC"],"open":0.00001146000000000000,"quoteCurrency":"BTC","sell":0.000013191,"sort":100,"symbol":"XMR-BTC","symbolCode":"XMR-BTC","takerCoefficient":2.000000,"takerFeeRate":0.001,"trading":true,"vol":28474.47280000000000000000,"volValue":0.37038038297340000000}}} -{"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":1698740324488,"data":{"averagePrice":0.00000037,"baseCurrency":"MTV","board":0,"buy":0.0000003641,"changePrice":0.00000004770000000000,"changeRate":0.1394,"close":0.0000003897,"datetime":1698740324483,"high":0.00000039450000000000,"lastTradedPrice":0.0000003897,"low":0.00000034200000000000,"makerCoefficient":2.000000,"makerFeeRate":0.001,"marginTrade":false,"mark":0,"market":"BTC","marketChange1h":{"changeRate":0,"high":0,"low":0,"open":0,"vol":0,"volValue":0},"marketChange24h":{"changePrice":0.00000004770000000000,"changeRate":0.1394,"high":0.00000039450000000000,"low":0.00000034200000000000,"open":0.00000034200000000000,"vol":316078.69700000000000000000,"volValue":0.11768519138877000000},"marketChange4h":{"changePrice":0.00000003290000000000,"changeRate":0.0922,"high":0.00000038970000000000,"low":0.00000035680000000000,"open":0.00000035680000000000,"vol":2309.46880000000000000000,"volValue":0.00089999999136000000},"markets":["BTC"],"open":0.00000034200000000000,"quoteCurrency":"BTC","sell":0.0000004022,"sort":100,"symbol":"MTV-BTC","symbolCode":"MTV-BTC","takerCoefficient":2.000000,"takerFeeRate":0.001,"trading":true,"vol":316078.69700000000000000000,"volValue":0.11768519138877000000}}} +{"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":1698740324488,"data":{"averagePrice":0.00000037,"baseCurrency":"ETH","board":0,"buy":0.0000003641,"changePrice":0.00000004770000000000,"changeRate":0.1394,"close":0.0000003897,"datetime":1698740324483,"high":0.00000039450000000000,"lastTradedPrice":0.0000003897,"low":0.00000034200000000000,"makerCoefficient":2.000000,"makerFeeRate":0.001,"marginTrade":false,"mark":0,"market":"BTC","marketChange1h":{"changeRate":0,"high":0,"low":0,"open":0,"vol":0,"volValue":0},"marketChange24h":{"changePrice":0.00000004770000000000,"changeRate":0.1394,"high":0.00000039450000000000,"low":0.00000034200000000000,"open":0.00000034200000000000,"vol":316078.69700000000000000000,"volValue":0.11768519138877000000},"marketChange4h":{"changePrice":0.00000003290000000000,"changeRate":0.0922,"high":0.00000038970000000000,"low":0.00000035680000000000,"open":0.00000035680000000000,"vol":2309.46880000000000000000,"volValue":0.00089999999136000000},"markets":["BTC"],"open":0.00000034200000000000,"quoteCurrency":"BTC","sell":0.0000004022,"sort":100,"symbol":"ETH-BTC","symbolCode":"ETH-BTC","takerCoefficient":2.000000,"takerFeeRate":0.001,"trading":true,"vol":316078.69700000000000000000,"volValue":0.11768519138877000000}}} {"type":"message","topic":"/market/snapshot:BTC","subject":"trade.snapshot","data":{"sequence":1698740324508,"data":{"averagePrice":0.00007307,"baseCurrency":"BTC","board":0,"buy":0.00008388,"changePrice":0.00001166000000000000,"changeRate":0.1630,"close":0.00008318,"datetime":1698740324437,"high":0.00008486000000000000,"lastTradedPrice":0.00008318,"low":0.00007152000000000000,"makerCoefficient":1.000000,"makerFeeRate":0.001,"marginTrade":false,"mark":0,"market":"USDT","marketChange1h":{"changePrice":-0.00000116000000000000,"changeRate":-0.0137,"high":0.00008434000000000000,"low":0.00008318000000000000,"open":0.00008434000000000000,"vol":189.33430000000000000000,"volValue":0.01578748292300000000},"marketChange24h":{"changePrice":0.00001166000000000000,"changeRate":0.1630,"high":0.00008486000000000000,"low":0.00007152000000000000,"open":0.00007152000000000000,"vol":17062.45450000000000000000,"volValue":1.33076678861000000000},"marketChange4h":{"changePrice":0.00000143000000000000,"changeRate":0.0174,"high":0.00008486000000000000,"low":0.00008175000000000000,"open":0.00008175000000000000,"vol":1752.55690000000000000000,"volValue":0.14543003812900000000},"markets":["BTC"],"open":0.00007152000000000000,"quoteCurrency":"USDT","sell":0.00008421,"sort":100,"symbol":"BTC-USDT","symbolCode":"BTC-USDT","takerCoefficient":1.000000,"takerFeeRate":0.001,"trading":true,"vol":17062.45450000000000000000,"volValue":1.33076678861000000000}}} diff --git a/exchanges/orderbook/orderbook_types.go b/exchanges/orderbook/orderbook_types.go index 229cdcd8..07b2a467 100644 --- a/exchanges/orderbook/orderbook_types.go +++ b/exchanges/orderbook/orderbook_types.go @@ -106,8 +106,8 @@ type Base struct { // Checks if the orderbook needs ID alignment as well as price alignment IDAlignment bool // Determines if there is a max depth of orderbooks and after an append we - // should remove any items that are outside of this scope. Bittrex and - // Kraken utilise this field. + // should remove any items that are outside of this scope. Kraken utilises + // this field. MaxDepth int // ChecksumStringRequired defines if the checksum is built from the raw // string representations of the price and amount. This helps alleviate any diff --git a/exchanges/support.go b/exchanges/support.go index 65875fb5..e198fc4f 100644 --- a/exchanges/support.go +++ b/exchanges/support.go @@ -21,7 +21,6 @@ var Exchanges = []string{ "bitflyer", "bitmex", "bitstamp", - "bittrex", "btc markets", "btse", "bybit", diff --git a/exchanges/trade/README.md b/exchanges/trade/README.md index ae3f2c40..ef191f3e 100644 --- a/exchanges/trade/README.md +++ b/exchanges/trade/README.md @@ -67,7 +67,6 @@ _b in this context is an `IBotExchange` implemented struct_ | Bithumb | Yes | Yes | No | | BitMEX | Yes | Yes | Yes | | Bitstamp | Yes | Yes | No | -| Bittrex | Yes | Yes | No | | BTCMarkets | Yes | Yes | No | | BTSE | Yes | Yes | No | | Bybit | Yes | Yes | Yes | diff --git a/testdata/configtest.json b/testdata/configtest.json index bfaacb57..2348827f 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -1021,84 +1021,6 @@ } ] }, - { - "name": "Bittrex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USDT", - "available": "1ECO-BTC,1ECO-USDT,1INCH-BTC,1INCH-ETH,1INCH-USD,1INCH-USDT,1PECO-BTC,1PECO-USDT,4ART-BTC,4ART-USDT,AAVE-BTC,AAVE-ETH,AAVE-EUR,AAVE-USD,AAVE-USDT,ABBC-BTC,ABYSS-BTC,ACH-USD,ACXT-USDT,ADA-BTC,ADA-ETH,ADA-EUR,ADA-USD,ADA-USDC,ADA-USDT,ADX-BTC,ADX-ETH,AGRS-BTC,AGRS-USDT,AGV-USDT,AKN-USDT,AKRO-BTC,AKT-BTC,AKTIO-USDT,AKT-USDT,ALGO-BTC,ALGO-USD,ALGO-USDT,ALTA-USDT,AMP-BTC,AMP-ETH,AMP-USD,AMP-USDT,ANKR-BTC,ANKR-ETH,ANKR-USD,ANT-BTC,ANT-ETH,ANTE-USDT,ANT-USD,APE-USD,APE-USDT,API3-USDT,APM-BTC,APM-USDT,APXP-USDT,AR-BTC,ARDR-BTC,ARDX-BTC,ARDX-ETH,ARDX-USDT,ARIA20-BTC,ARIA20-USD,ARK-BTC,ARTIC-USDT,ARTII-BTC,AR-USD,ARV-USDT,ARW-USDT,ASM-USDT,ATOM-BTC,ATOM-ETH,ATOM-USD,ATOM-USDT,ATRI-USDT,ATTR-USDT,AUDT-USDT,AVAX-BTC,AVAX-ETH,AVAX-USD,AVAX-USDT,AVT-ETH,AVT-USDT,AXS-USD,AXS-USDT,B2M-USDT,BAAS-BTC,BAAS-USDT,BADGER-USD,BAL-BTC,BAL-ETH,BAL-EUR,BAL-USD,BAL-USDT,BAND-BTC,BAND-ETH,BAND-EUR,BAND-USD,BAND-USDT,BAT-BTC,BAT-ETH,BAT-USD,BAT-USDT,BAX-ETH,BAX-USDT,BBC-BTC,BBC-USDT,BBF-USDT,BCH-BTC,BCH-ETH,BCH-EUR,BCH-USD,BCH-USDT,BEE-USDT,BERRY-USDT,BEST-USDT,BFC-BTC,BIFI-BTC,BIOT-USDT,BIST-USDT,BITCI-USDT,BLK-BTC,BLOCK-BTC,BLOCK-USDT,BMP-BTC,BMP-USDT,BNA-USDT,BNS-BTC,BNS-USDT,BNT-BTC,BNT-ETH,BNT-USD,BOA-BTC,BOA-USDT,BOND-ETH,BONDLY-BTC,BONDLY-ETH,BONDLY-USDT,BOND-USDT,BOSON-BTC,BOSON-USDT,BRZ-BTC,BRZ-USDT,BST-BTC,BST-USDT,BSV-BTC,BSV-ETH,BSV-EUR,BSV-USD,BSV-USDT,BTBS-USDT,BTC-EUR,BTC-USD,BTC-USDC,BTC-USDT,BTCV-BTC,BTCV-USDT,BTD-USDT,BTM-BTC,BTR-BTC,BTRST-USD,BTS-BTC,BTTOLD-BTC,BTTOLD-USDT,BTU-BTC,CADX-BTC,CAMP-BTC,CAMP-USDT,CAST-USDT,CBANK-USDT,CBC-BTC,CBC-USDT,CDEX-USDT,CEDS-USDT,CEL-BTC,CEL-ETH,CELO-BTC,CELO-ETH,CELO-EUR,CELO-USD,CELO-USDT,CEL-USD,CEL-USDT,CGT-BTC,CGT-USDT,CHNG-USDT,CIND-USDT,CIV-USDT,CKB-BTC,CKB-USD,CKB-USDT,CLI-USDT,CLT-BTC,CLT-USDT,CMCX-USDT,CND-BTC,CNTM-BTC,CNTM-USDT,COMP-BTC,COMP-ETH,COMP-USD,COMP-USDT,COT-BTC,COT-ETH,COT-USDT,COVN-BTC,COVN-USDT,CPC-BTC,CRFI-USDT,CRO-BTC,CRO-ETH,CRO-EUR,CRO-USD,CRO-USDT,CRTS-USDT,CRV-BTC,CRV-ETH,CRV-USDT,CRW-BTC,CRW-USDT,CSC-USDT,CTC-BTC,CTPL-USDT,CTXC-BTC,CUDOS-BTC,CUDOS-USDT,CURE-BTC,CURIO-USDT,CUSD-BTC,CUSD-ETH,CUSD-USDT,CVC-BTC,CVC-ETH,CWC-BTC,CWD-USDT,CWEB-USDT,CYCLUB-USDT,DAF-ETH,DAF-USDT,DAI-BTC,DAI-ETH,DAI-USD,DAI-USDT,DASH-BTC,DASH-ETH,DASH-USD,DASH-USDT,DAWN-BTC,DCR-BTC,DCR-USD,DCR-USDT,DECE-USDT,DEP-BTC,DEP-USDT,DEXA-USDT,DFCH-USDT,DFI-BTC,DFI-ETH,DFI-EUR,DFI-USDT,DGB-BTC,DGB-ETH,DGB-EUR,DGB-USD,DGB-USDT,DMR-ETH,DMR-USDT,DMT-BTC,DMT-ETH,DMTR-USDT,DNT-BTC,DOGE-BTC,DOGE-ETH,DOGE-EUR,DOGE-USD,DOGE-USDC,DOGE-USDT,DOT-BTC,DOT-ETH,DOT-EUR,DOT-USD,DOT-USDT,DRC-ETH,DRCM-USDT,DRE-USDT,DRGN-BTC,DST-USDT,DTX-ETH,DTX-USDT,DUSK-BTC,DUSK-USDT,DVI-BTC,DVI-USDT,DVK-USDT,EAC-USDT,ECELL-BTC,ECELL-USDT,ECOC-BTC,ECOC-USDT,EDR-USDT,ELA-BTC,ELAMA-BTC,ELA-USDT,ELT-USDT,EMC2-BTC,EMC2-ETH,ENG-BTC,ENG-ETH,ENJ-BTC,ENJ-ETH,ENJ-USD,ENJ-USDT,ENS-USDT,EOS-BTC,EOS-ETH,EOS-USD,EOS-USDT,EPTT-USDT,EQX-BTC,EQX-ETH,EQX-USDT,ETC-BTC,ETC-ETH,ETC-USD,ETC-USDT,ETHA-USDT,ETH-BTC,ETH-EUR,ETH-USD,ETH-USDC,ETH-USDT,ETHW-USD,ETHW-USDT,ETM-USDT,EWC-USDT,EXCL-BTC,EXE-BTC,EXE-USDT,EXP-BTC,EXVA-USDT,FCT2-BTC,FDM-USDT,FEVR-USDT,FIL-BTC,FIL-ETH,FIL-USD,FIL-USDT,FIRO-USDT,FIT-BTC,FLIXX-USDT,FLO-BTC,FLUX-USDT,FNX-BTC,FNX-USDT,FOL-BTC,FOL-USDT,FTC-BTC,FTC-USDT,FTM-USD,FTM-USDT,FUSE-USDT,FX-BTC,FX-ETH,FXS-USDT,FX-USDT,GALA-USD,GALA-USDT,GAME-BTC,GAME-USDT,GAZE-ETH,GAZE-USDT,GBIT-USDT,GBPT-BTC,GBPT-ETH,GBPT-USDC,GBYTE-BTC,GDT-USDT,GEO-BTC,GET-BTC,GET-USDT,GIGX-USDT,GLEEC-BTC,GLEEC-USDT,GLM-BTC,GLM-ETH,GMT-USDT,GNC-BTC,GNC-USDT,GNO-BTC,GNO-ETH,GNY-BTC,GO-BTC,GOLD-USDT,GPX-USDT,GPYX-BTC,GPYX-USDT,GRNC-USDT,GRS-BTC,GRT-BTC,GRT-ETH,GRT-EUR,GRT-USD,GRT-USDT,GST-BTC,GST-USDT,GTC-USD,GXC-BTC,GXC-USDT,HBAR-BTC,HBAR-ETH,HBAR-USD,HBAR-USDT,HBD-BTC,HDAO-BTC,HDAO-USDT,HEDG-BTC,HIVE-BTC,HIVE-USD,HIVE-USDT,HLM-USDT,HNS-BTC,HNS-ETH,HNS-USDT,HOTCROSS-USDT,HRTS-USD,HRTS-USDT,HTML-USDT,HXRO-BTC,HXRO-USDT,HYDRO-BTC,HYVE-USDT,ICA-USDT,ICX-BTC,IGNIS-BTC,IGNIS-USDT,INSTAR-BTC,INV-ETH,INX-BTC,INXT-BTC,INXT-USDT,INX-USDT,IOC-BTC,IOST-BTC,IOTA-BTC,IOTA-USD,IOTA-USDT,IOTX-BTC,IOTX-USD,IOTX-USDT,IQO-USDT,IQQ-BTC,IQQ-USDT,IQ-USDT,IRIS-BTC,IRIS-USDT,JAM-USDT,JASMY-ETH,JASMY-USDT,JGN-USDT,JOB-BTC,JOB-USDT,KAI-BTC,KAI-USDT,KBH-USDT,KDA-BTC,KDA-USD,KDA-USDT,KIN-USDT,KLAY-BTC,KLAY-USDT,KLEVA-USDT,KLV-BTC,KLV-USDT,KMD-BTC,KMD-USD,KMD-USDT,KNC-BTC,KNC-ETH,KNC-EUR,KNC-USD,KNC-USDT,KOK-BTC,KOK-USDT,KRRX-USDT,KSM-BTC,KSM-ETH,KSM-USD,KSM-USDT,KUSD-USDC,KUSD-USDT,LAND-USDT,LBC-BTC,LBC-ETH,LBC-USD,LBC-USDT,LBL-USDT,LGCY-USDT,LINK-BTC,LINK-ETH,LINK-USD,LINK-USDT,LOOM-BTC,LOON-BTC,LOON-USDT,LPNT-USDT,LPT-USDT,LRC-BTC,LRC-USD,LSK-BTC,LSK-USDT,LTC-BTC,LTC-ETH,LTC-USD,LTC-USDT,LUCY-BTC,LUCY-USDT,LWC-ETH,LWC-USDT,MAID-BTC,MANA-BTC,MANA-ETH,MANA-USD,MARS4-ETH,MARS4-USDT,MATIC-BTC,MATIC-ETH,MATIC-USD,MATIC-USDT,MCCX-USDT,MCH-USDT,MDC-BTC,MDT-BTC,MDT-USDT,ME-BTC,MED-BTC,MED-USDT,MEME-BTC,MER-BTC,METADIUM-BTC,MF1-USDT,MFT-BTC,MFT-USDT,MF-USDT,MIMO-BTC,MIM-USDT,MINE-USDT,MINT-ETH,MINT-USDT,MKR-BTC,MKR-ETH,MKR-USDT,MMAON-USDT,MNFT-ETH,MNFT-USDT,MNW-BTC,MNW-ETH,MNW-USDT,MODEX-ETH,MODEX-USDT,MOGX-USDT,MONA-BTC,MONA-USDT,MORE-BTC,MOR-USDT,MOV-USDT,MPC-USDT,MPT-USDT,MQL-USDT,MSB-USDT,MTC-BTC,MTL-BTC,MTRA-USDT,MTSP-USDT,MTS-USDT,MUE-BTC,MUNT-BTC,MVC-USDT,MYCE-BTC,MYCE-USDT,MYID-USDT,MYST-BTC,MYST-USDT,NAV-BTC,NDAU-USDT,NEO-BTC,NEO-ETH,NEO-USDT,NFTX-BTC,NFTX-ETH,NFTX-USDT,NGC-BTC,NIFT-USDT,NKCLC-USDT,NKN-BTC,NKN-USDT,NMR-BTC,NMR-ETH,NMR-USDT,NPT-USDT,NVT-BTC,NVT-USDT,NXS-BTC,NXT-BTC,O3-ETH,O3-USDT,OCEAN-BTC,OCEAN-USDT,OGN-BTC,OGN-ETH,OGT-BTC,OGT-USDT,OK-BTC,OMG-BTC,OMG-ETH,OMG-USD,OMG-USDT,ONG-BTC,ONSTON-USDT,ONT-BTC,ONT-USDT,ORBS-BTC,OXEN-BTC,OXEN-USDT,OXT-BTC,OXT-USDT,PANDO-USDT,PAR-BTC,PART-BTC,PAR-USDT,PAXG-USD,PAXG-USDT,PAY-BTC,PAY-ETH,PGX-USDT,PHNX-BTC,PHNX-USDT,PINK-USDT,PIST-USDT,PIVX-BTC,PKR-USDT,PKT-BTC,PKT-USDT,PLA-BTC,PLA-EUR,PLA-USD,PLA-USDT,PLAY-BTC,PMA-BTC,PMA-USDT,POLC-ETH,POLC-USDT,POLL-USDT,POPK-USDT,POT-BTC,POWR-BTC,POWR-ETH,PPAY-BTC,PPAY-USD,PPAY-USDT,PPC-BTC,PROM-BTC,PROM-USDT,PROS-USDT,PRO-USD,PRO-USDC,PRO-USDT,PRT-USDT,PTOY-BTC,PTOY-USDT,PUNDIX-BTC,PUNDIX-ETH,PUNDIX-USDT,PXP-USDT,PYR-BTC,PYR-USDT,QLC-BTC,QLC-USDT,QNT-BTC,QNT-ETH,QNT-USD,QRL-BTC,QTUM-BTC,QTUM-ETH,QTUM-USDT,R1-USDT,RAD-USD,RAMP-BTC,RAMP-ETH,RAMP-USDT,RDD-USDT,REAL-BTC,REAL-USDT,REN-BTC,RENBTC-BTC,RENBTC-ETH,RENBTC-EUR,RENBTC-USD,RENBTC-USDT,REN-ETH,REN-EUR,REN-USD,REN-USDT,REPV2-BTC,REPV2-ETH,REV-BTC,REV-USDT,REVV-BTC,REVV-USDT,RFOX-BTC,RFOX-USDT,RGT-BTC,RGT-ETH,RGT-USDT,RLC-BTC,RLC-USD,RLY-USD,RNB-BTC,RNDR-USDT,ROC-USDT,ROOK-BTC,ROOK-ETH,ROOK-USDT,RSR-BTC,RSR-USDT,RSV-USD,RSV-USDT,RTH-USDT,RVC-BTC,RVC-USDT,RVN-BTC,RVN-ETH,RVN-USD,RVN-USDT,SAND-BTC,SAND-USD,SAND-USDT,SATT-USDT,SBD-BTC,SBT-USDT,SC-BTC,SC-ETH,SC-USD,SC-USDT,SENSO-BTC,SENSO-ETH,SG-USDT,SHIB-USD,SHR-BTC,SHR-USDT,SHX-BTC,SHX-USDT,SIG-BTC,SIG-ETH,SIGNA-BTC,SIGNA-USDT,SIG-USDT,SIRS-BTC,SIRS-USDT,SKL-USD,SKL-USDT,SLICE-BTC,SLICE-USDT,SLS-BTC,SMARTCREDIT-USDT,SMBSWAP-BTC,SMBSWAP-ETH,SMBSWAP-USDT,SMG-USDT,SML-USDT,SNT-BTC,SNT-ETH,SNX-BTC,SNX-ETH,SNX-USD,SNX-USDT,SOL-BTC,SOL-EUR,SOL-USD,SOL-USDT,SOLVE-BTC,SOLVE-ETH,SOLVE-USD,SOLVE-USDT,SPC-BTC,SPC-USDT,SPHR-BTC,SPI-BTC,SPI-USDT,SPWN-ETH,SPWN-USDT,SRM-USDT,SRN-BTC,SRN-ETH,SSX-BTC,SSX-USDT,STCCOIN-BTC,STCCOIN-USDT,STEEM-BTC,STEEM-USDT,STMX-BTC,STMX-ETH,STMX-EUR,STMX-USD,STORJ-BTC,STORJ-USD,STPT-BTC,STRAX-BTC,STRAX-ETH,STRK-BTC,STRK-USD,STRK-USDT,SUKU-BTC,SUKU-USDT,SUSHI-BTC,SUSHI-ETH,SUSHI-EUR,SUSHI-USD,SUSHI-USDT,SXP-BTC,SYLO-USDT,SYS-BTC,SYS-USDT,TCR-USDT,TEA-BTC,TEA-USDT,TEDDY-USDT,TFBX-USDT,TFC-BTC,TFC-USDT,THC-BTC,TLM-USDT,TNC-BTC,TOKKI-USDT,TOM-USDT,TON-USD,TRAC-BTC,TRAC-ETH,TRAC-USDT,TRIX-USDT,TRX-BTC,TRX-ETH,TRX-EUR,TRX-USD,TRX-USDT,TRZ-USDT,TUSD-BTC,TUSD-ETH,TUSD-USD,TUSD-USDT,TXA-USDT,TYB-USDT,TYC-BTC,TYC-USDT,TZBTC-BTC,TZBTC-USDT,UBQ-BTC,UBQ-USDT,UBT-BTC,UBT-ETH,UBT-EUR,UMA-BTC,UMA-ETH,UMA-EUR,UMA-USD,UMA-USDT,UNI-BTC,UNI-ETH,UNI-EUR,UNI-USD,UNI-USDT,UNIX-BTC,UNIX-ETH,UNIX-USDT,UPCO2-BTC,UPCO2-USDT,UPEUR-BTC,UPP-BTC,UPT-BTC,UPUSD-BTC,UPUSD-USDT,UPXAU-BTC,UPXAU-USDT,UQC-BTC,UQC-USDT,URQA-BTC,URQA-USDT,USDC-BTC,USDC-ETH,USDC-EUR,USDC-USD,USDC-USDT,USD-EUR,USDN-BTC,USDN-USDT,USDP-BTC,USDP-USD,USDS-BTC,USDS-USD,USDT-EUR,USDT-USD,USDT-USDC,UTK-BTC,UZRS-USDT,VAL-BTC,VAL-USDT,VANY-BTC,VANY-USDT,VBK-BTC,VCK-USDT,VEE-BTC,VEMP-USDT,VET-BTC,VET-ETH,VET-USD,VET-USDT,VIA-BTC,VID-BTC,VIL-BTC,VITE-BTC,VLX-BTC,VLX-USDT,VOLTINU-USDT,VRA-BTC,VRA-ETH,VRA-USDT,VRC-BTC,VSP-ETH,VSP-USDT,VTC-BTC,VVT-USDT,WACME-ETH,WACME-USDT,WAVES-BTC,WAVES-ETH,WAVES-USDT,WAXE-USDT,WAXP-BTC,WAXP-ETH,WAXP-USD,WAXP-USDT,WBTC-BTC,WBTC-ETH,WBTC-USDT,WEC-USDT,WEMIX-USDT,WICC-BTC,WICC-USDT,WINGS-BTC,WSB-USDT,WSTRM-USDT,WXBTC-BTC,WXBTC-USDT,XAI-USDT,XBN-USDT,XCAD-USDT,XCF-BTC,XCF-USDT,XCN-BTC,XCN-ETH,XCN-EUR,XCN-USD,XCN-USDT,XDC-BTC,XDC-EUR,XDC-USDT,XDN-USDT,XELS-BTC,XELS-USDT,XEM-BTC,XEM-ETH,XEM-USD,XEM-USDT,XEP-USDT,XGOLD-USDT,XLM-BTC,XLM-ETH,XLM-EUR,XLM-USD,XLM-USDT,XMY-USDT,XRP-BTC,XRP-ETH,XRP-EUR,XRP-USD,XRP-USDC,XRP-USDT,XSILV-USDT,XST-BTC,XTP-BTC,XTZ-BTC,XTZ-ETH,XTZ-USD,XTZ-USDT,XVG-BTC,XVG-ETH,XVG-USDT,XWC-BTC,XWC-USDT,XYM-BTC,XYM-ETH,XYM-USDT,XYO-USD,XYO-USDT,YEFI-USDT,YFI-USD,YFL-ETH,YFL-USDT,YLD-BTC,YLD-USDT,YOU-USDT,ZEC-BTC,ZEC-ETH,ZEC-USD,ZEC-USDT,ZEN-BTC,ZEN-USD,ZEN-USDT,ZIL-BTC,ZILD-USDT,ZIL-USD,ZKP-ETH,ZKP-USDT,ZK-USDT,ZPTC-USDC,ZRX-BTC,ZRX-ETH,ZRX-USD,ZRX-USDT,ZUSD-USDT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, { "name": "BTSE", "enabled": true, @@ -2009,7 +1931,7 @@ }, "margin": { "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MTV-BTC", + "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MTV-BTC,ETH-BTC", "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", "requestFormat": { "uppercase": true, diff --git a/testdata/exchangelist.csv b/testdata/exchangelist.csv index 92dfea9f..6425a47e 100644 --- a/testdata/exchangelist.csv +++ b/testdata/exchangelist.csv @@ -5,7 +5,6 @@ bitflyer, bithumb, bitmex, bitstamp, -bittrex, btc markets, btse, coinbasepro,