exchanges: remove Coinbene exchange support (#849)

* exchanges: remove Coinbene exchange support

* RM Coinbene from apichecker backup
This commit is contained in:
Adrian Gallagher
2021-11-29 11:34:35 +11:00
committed by GitHub
parent 0c5a666854
commit 02afa1e98b
25 changed files with 1 additions and 4929 deletions

View File

@@ -29,7 +29,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| BTCMarkets | Yes | Yes | NA |
| BTSE | Yes | Yes | NA |
| CoinbasePro | Yes | Yes | No|
| Coinbene | Yes | Yes | No |
| COINUT | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| FTX | Yes | Yes | No |

View File

@@ -78,28 +78,6 @@
},
"Disabled": false
},
{
"Name": "CoinbeneSpot",
"CheckType": "GitHub Sha Check",
"Data": {
"GitHubData": {
"Repo": "Coinbene/API-SPOT-v2-Documents",
"Sha": "e9135a782ba6016bcf008778be368882ad7c784d"
}
},
"Disabled": false
},
{
"Name": "CoinbeneSwap",
"CheckType": "GitHub Sha Check",
"Data": {
"GitHubData": {
"Repo": "Coinbene/API-SWAP-Documents",
"Sha": "6b7871dae4d2af028a33dde956fbce101e2f9acd"
}
},
"Disabled": false
},
{
"Name": "Coinut",
"CheckType": "GitHub Sha Check",

View File

@@ -83,28 +83,6 @@
},
"Disabled": false
},
{
"Name": "CoinbeneSpot",
"CheckType": "GitHub Sha Check",
"Data": {
"GitHubData": {
"Repo": "Coinbene/API-SPOT-v2-Documents",
"Sha": "e9135a782ba6016bcf008778be368882ad7c784d"
}
},
"Disabled": false
},
{
"Name": "CoinbeneSwap",
"CheckType": "GitHub Sha Check",
"Data": {
"GitHubData": {
"Repo": "Coinbene/API-SWAP-Documents",
"Sha": "6b7871dae4d2af028a33dde956fbce101e2f9acd"
}
},
"Disabled": false
},
{
"Name": "Coinut",
"CheckType": "GitHub Sha Check",

View File

@@ -83,28 +83,6 @@
},
"Disabled": false
},
{
"Name": "CoinbeneSpot",
"CheckType": "GitHub Sha Check",
"Data": {
"GitHubData": {
"Repo": "Coinbene/API-SPOT-v2-Documents",
"Sha": "e9135a782ba6016bcf008778be368882ad7c784d"
}
},
"Disabled": false
},
{
"Name": "CoinbeneSwap",
"CheckType": "GitHub Sha Check",
"Data": {
"GitHubData": {
"Repo": "Coinbene/API-SWAP-Documents",
"Sha": "6b7871dae4d2af028a33dde956fbce101e2f9acd"
}
},
"Disabled": false
},
{
"Name": "Coinut",
"CheckType": "GitHub Sha Check",

View File

@@ -1,106 +0,0 @@
{{define "exchanges coinbene" -}}
{{template "header" .}}
## Coinbene Exchange
### Current Features
+ REST Support
+ Websocket Support
### 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 c exchange.IBotExchange
for i := range Bot.Exchanges {
if Bot.Exchanges[i].GetName() == "Coinbene" {
c = Bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := c.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := c.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 := c.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := c.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := c.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 := c.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
resp, err := c.SubmitOrder(...)
if err != nil {
// Handle error
}
```
### How to do Websocket public/private calls
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### Please click GoDocs chevron above to view current GoDoc information for this package
{{template "contributions"}}
{{template "donations" .}}
{{end}}

View File

@@ -51,7 +51,6 @@ _b in this context is an `IBotExchange` implemented struct_
| Bittrex | Yes | Yes | No |
| BTCMarkets | Yes | Yes | No |
| BTSE | Yes | Yes | No |
| Coinbene | Yes | Yes | No |
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | No |
| Exmo | Yes | NA | No |

View File

@@ -30,7 +30,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| BTCMarkets | Yes | Yes | NA |
| BTSE | Yes | Yes | NA |
| CoinbasePro | Yes | Yes | No|
| Coinbene | Yes | Yes | No |
| COINUT | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| FTX | Yes | Yes | No |

View File

@@ -86,11 +86,6 @@
"clientID": "ClientID",
"otpSecret": "-"
},
"coinbene": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"coinut": {
"key": "Key",
"clientID": "ClientID",

View File

@@ -1208,95 +1208,6 @@
}
]
},
{
"name": "Coinbene",
"enabled": true,
"verbose": false,
"httpTimeout": 15000000000,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"websocketTrafficTimeout": 30000000000,
"websocketOrderbookBufferLimit": 5,
"baseCurrencies": "USD",
"currencyPairs": {
"assetTypes": [
"spot",
"perpetualswap"
],
"pairs": {
"perpetualswap": {
"enabled": "BTC/USDT",
"available": "LTC/USDT,EOS/USDT,ETH/USDT,BTC/USDT",
"requestFormat": {
"uppercase": true
},
"configFormat": {
"uppercase": true,
"delimiter": "/"
}
},
"spot": {
"enabled": "BTC/USDT",
"available": "ABBC/BTC,ABBC/USDT,ABT/ETH,ABT/USDT,ABYSS/ETH,ACDC/USDT,ADA/USDT,ADK/BTC,ADN/BTC,AE/BTC,AE/USDT,AIDOC/BTC,AIDUS/BTC,AION/BTC,AIPE/USDT,ALI/ETH,ALX/ETH,AMDC/USDT,APL/ETH,ATX/BTC,BAAS/BTC,BAT/BTC,BCH/USDT,BCT/USDT,BETHER/ETH,BEZ/BTC,BGC/USDT,BKG/BTC,BNB/USDT,BNT/BTC,BOA/USDT,BR/USDT,BSTN/ETH,BSV/BTC,BSV/USDT,BTC/USDT,BTCV/BTC,BTNT/BTC,BVT/ETH,CCC/ETH,CCE/USDT,CEDEX/ETH,CFT/USDT,CMT/ETH,CMT/USDT,CNN/BTC,CNN/ETH,CNN/USDT,CONI/USDT,COSM/BTC,COSM/ETH,CPM1/USDT,CPS/BTC,CREDO/ETH,CRN/BTC,CSCC/USDT,CTCN/USDT,CTXC/ETH,CUST/USDT,CVC/BTC,DASH/USDT,DDAM/ETH,DDAM/USDT,DENT/BTC,DGD/BTC,DTA/ETH,DVC/ETH,EBC/BTC,EBC/ETH,EBC/USDT,ECA/BTC,ECP/BTC,EDC/BTC,EDR/ETH,EHT/USDT,ELF/BTC,EOS/BTC,EOS/USDT,EQUAD/BTC,ESH/BTC,ETC/BTC,ETC/USDT,ETH/BTC,ETH/USDT,ETK/BTC,ETN/BTC,EXO/USDT,FAB/ETH,FCR/USDT,FND/ETH,FNKOS/ETH,FTN/BTC,FTN/USDT,FTT/BTC,FXT/ETH,GDC/BTC,GDC/ETH,GDC/USDT,GETX/ETH,GOM2/USDT,GRAM/USDT,GRN/BTC,GUSD/USDT,GVT/BTC,HAPPY/BTC,HDAC/BTC,HMB/USDT,HNB/USDT,HTDF/USDT,HT/USDT,HUP/USDT,INCX/ETH,IOST/BTC,IOTE/USDT,ISR/ETH,IVY/ETH,JOB/BTC,KBC/BTC,KBC/USDT,KMD/BTC,KNT/ETH,KST/BTC,KUKY/BTC,LAMB/USDT,LATX/BTC,LBK/BTC,LINK/BTC,LOOM/BTC,LP/USDT,LTC/BTC,LTC/USDT,LUC/ETH,LUCKY/USDT,LUX/BTC,LVTC/ETH,MC/USDT,MIB/BTC,MINX/BTC,MOAC/USDT,MTC/BTC,MTN/ETH,MT/USDT,MVL/ETH,MXM/ETH,MXM/USDT,MZG/USDT,NANO/BTC,NBAI/ETH,NEO/BTC,NEO/USDT,NOBS/BTC,NPXS/USDT,NTY/ETH,ODC/USDT,OKB/USDT,OMG/BTC,OMX/ETH,OVC/ETH,OZX/ETH,PAT/ETH,PAX/USDT,PLF/USDT,PMA/ETH,POLL/BTC,POLY/BTC,PPT/BTC,PSM/BTC,PWT/USDT,QKC/BTC,QTUM/BTC,QTUM/USDT,RBTC/BTC,RCOIN/BTC,RCOIN/USDT,REP/BTC,REV/BTC,RIF/BTC,SBT/USDT,SCC/BTC,SEN/BTC,SHE/BTC,SHVR/BTC,SIM/BTC,SKB/BTC,SKM/ETH,SKYM/USDT,SLT/ETH,SMARTUP/USDT,SMART/USDT,SORO/USDT,SRCOIN/BTC,SRCOIN/ETH,STORJ/BTC,SWET/BTC,SWTC/USDT,SWYFTT/BTC,TCT/BTC,TEN/BTC,TEN/ETH,THC/USDT,TIB/BTC,TMTG/BTC,TOC/USDT,TOSC/BTC,TRUE/ETH,TRX/BTC,TRX/USDT,TWEE/USDT,UTNP/BTC,VEEN/BTC,VME/BTC,VME/ETH,VSC/ETH,W12/BTC,W12/ETH,WBL/BTC,WBX/USDT,WFX/BTC,XEM/BTC,XLM/BTC,XMR/BTC,XNK/ETH,XRP/BTC,XRP/USDT,XSR/USDT,YAP/BTC,YAP/USDT,YTA/USDT,ZAT/ETH,ZEC/BTC,ZEC/USDT,ZRX/BTC",
"requestFormat": {
"uppercase": true,
"delimiter": "/"
},
"configFormat": {
"uppercase": true,
"delimiter": "/"
}
}
}
},
"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",
"clientID": "ClientID"
},
"credentialsValidator": {
"requiresKey": true,
"requiresSecret": true
}
},
"features": {
"supports": {
"restAPI": true,
"restCapabilities": {
"autoPairUpdates": true
},
"websocketAPI": true,
"websocketCapabilities": {}
},
"enabled": {
"autoPairUpdates": true,
"websocketAPI": false
}
},
"bankAccounts": [
{
"enabled": false,
"bankName": "",
"bankAddress": "",
"bankPostalCode": "",
"bankPostalCity": "",
"bankCountry": "",
"accountName": "",
"accountNumber": "",
"swiftCode": "",
"iban": "",
"supportedCurrencies": ""
}
]
},
{
"name": "FTX",
"enabled": true,

View File

@@ -209,7 +209,6 @@ Yes means supported, No means not yet implemented and NA means protocol unsuppor
| Exmo | Yes | NA | NA |
| FTX | Yes | Yes | No | // <-------- new exchange
| CoinbasePro | Yes | Yes | No|
| Coinbene | Yes | No | No |
| GateIO | Yes | Yes | NA |
| Gemini | Yes | Yes | No |
| HitBTC | Yes | Yes | No |
@@ -238,7 +237,6 @@ var Exchanges = []string{
"btc markets",
"btse",
"coinbasepro",
"coinbene",
"coinut",
"exmo",
"ftx", // <-------- new exchange

View File

@@ -53,7 +53,6 @@ $ ./gctcli withdrawcryptofunds --exchange=ftx --currency=USDT --address=TJU9piX2
| BTCMarkets | No | No| NA |
| BTSE | No | No | Only through website |
| CoinbasePro | No | No | No|
| Coinbene | Yes | Yes | Addresses must be created via their website first |
| COINUT | No | No | NA |
| Exmo | Yes | Yes | Addresses must be created via their website first |
| FTX | Yes | Yes | |

View File

@@ -75,7 +75,6 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
| Bittrex | |
| BTSE | Y |
| Coinbase Pro | Y |
| Coinbene | Y |
| Coinut | |
| Exmo | |
| GateIO | Y |

View File

@@ -17,7 +17,6 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/btcmarkets"
"github.com/thrasher-corp/gocryptotrader/exchanges/btse"
"github.com/thrasher-corp/gocryptotrader/exchanges/coinbasepro"
"github.com/thrasher-corp/gocryptotrader/exchanges/coinbene"
"github.com/thrasher-corp/gocryptotrader/exchanges/coinut"
"github.com/thrasher-corp/gocryptotrader/exchanges/exmo"
"github.com/thrasher-corp/gocryptotrader/exchanges/ftx"
@@ -160,8 +159,6 @@ func (m *ExchangeManager) NewExchangeByName(name string) (exchange.IBotExchange,
exch = new(btcmarkets.BTCMarkets)
case "btse":
exch = new(btse.BTSE)
case "coinbene":
exch = new(coinbene.Coinbene)
case "coinut":
exch = new(coinut.COINUT)
case "exmo":

View File

@@ -82,7 +82,7 @@ func TestExchangeManagerRemoveExchange(t *testing.T) {
func TestNewExchangeByName(t *testing.T) {
m := SetupExchangeManager()
exchanges := []string{"binance", "bitfinex", "bitflyer", "bithumb", "bitmex", "bitstamp", "bittrex", "btc markets", "btse", "coinbene", "coinut", "exmo", "coinbasepro", "ftx", "gateio", "gemini", "hitbtc", "huobi", "itbit", "kraken", "lbank", "localbitcoins", "okcoin international", "okex", "poloniex", "yobit", "zb", "fake"}
exchanges := []string{"binance", "bitfinex", "bitflyer", "bithumb", "bitmex", "bitstamp", "bittrex", "btc markets", "btse", "coinut", "exmo", "coinbasepro", "ftx", "gateio", "gemini", "hitbtc", "huobi", "itbit", "kraken", "lbank", "localbitcoins", "okcoin international", "okex", "poloniex", "yobit", "zb", "fake"}
for i := range exchanges {
exch, err := m.NewExchangeByName(exchanges[i])
if err != nil && exchanges[i] != "fake" {

View File

@@ -1,140 +0,0 @@
# GoCryptoTrader package Coinbene
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/coinbene)
[![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 coinbene 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)
## Coinbene Exchange
### Current Features
+ REST Support
+ Websocket Support
### 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 c exchange.IBotExchange
for i := range Bot.Exchanges {
if Bot.Exchanges[i].GetName() == "Coinbene" {
c = Bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := c.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := c.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 := c.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := c.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := c.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 := c.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
resp, err := c.SubmitOrder(...)
if err != nil {
// Handle error
}
```
### How to do Websocket public/private calls
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### 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
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***

File diff suppressed because it is too large Load Diff

View File

@@ -1,892 +0,0 @@
package coinbene
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"
"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 your own keys here for due diligence testing
const (
testAPIKey = ""
testAPISecret = ""
canManipulateRealOrders = false
spotTestPair = "BTC/USDT"
swapTestPair = "BTC-SWAP"
)
var c Coinbene
func TestMain(m *testing.M) {
c.SetDefaults()
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
log.Fatal(err)
}
coinbeneConfig, err := cfg.GetExchangeConfig("Coinbene")
if err != nil {
log.Fatal(err)
}
coinbeneConfig.API.AuthenticatedWebsocketSupport = true
coinbeneConfig.API.AuthenticatedSupport = true
coinbeneConfig.API.Credentials.Secret = testAPISecret
coinbeneConfig.API.Credentials.Key = testAPIKey
c.Websocket = sharedtestvalues.NewTestWebsocket()
err = c.Setup(coinbeneConfig)
if err != nil {
log.Fatal(err)
}
os.Exit(m.Run())
}
func areTestAPIKeysSet() bool {
return c.AllowAuthenticatedRequest()
}
func TestStart(t *testing.T) {
t.Parallel()
err := c.Start(nil)
if !errors.Is(err, common.ErrNilPointer) {
t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrNilPointer)
}
var testWg sync.WaitGroup
err = c.Start(&testWg)
if err != nil {
t.Fatal(err)
}
testWg.Wait()
}
func TestGetAllPairs(t *testing.T) {
t.Parallel()
_, err := c.GetAllPairs(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetPairInfo(t *testing.T) {
t.Parallel()
_, err := c.GetPairInfo(context.Background(), spotTestPair)
if err != nil {
t.Error(err)
}
}
func TestGetOrderbook(t *testing.T) {
t.Parallel()
_, err := c.GetOrderbook(context.Background(), spotTestPair, 100)
if err != nil {
t.Error(err)
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := c.GetTicker(context.Background(), spotTestPair)
if err != nil {
t.Error(err)
}
}
func TestGetTrades(t *testing.T) {
t.Parallel()
_, err := c.GetTrades(context.Background(), spotTestPair, 100)
if err != nil {
t.Error(err)
}
}
func TestGetAcounntBalances(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.GetAccountBalances(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetAccountAssetBalance(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.GetAccountAssetBalance(context.Background(), currency.BTC.String())
if err != nil {
t.Error(err)
}
}
func TestPlaceOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.PlaceSpotOrder(context.Background(),
1,
1,
spotTestPair,
order.Buy.Lower(),
order.Limit.Lower(),
"Sup3rAw3s0m3Cl13ntiDH",
0,
)
if err != nil {
t.Error(err)
}
}
func TestPlaceOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.PlaceSpotOrders(context.Background(),
[]PlaceOrderRequest{
{
1,
1,
spotTestPair,
order.Buy.Lower(),
order.Limit.Lower(),
"Sup3rAw3s0m3Cl13ntiDH",
0,
},
})
if err != nil {
t.Error(err)
}
}
func TestFetchOpenOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.FetchOpenSpotOrders(context.Background(), spotTestPair)
if err != nil {
t.Error(err)
}
}
func TestFetchClosedOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.FetchClosedOrders(context.Background(), spotTestPair, "")
if err != nil {
t.Error(err)
}
}
func TestFetchOrderInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.FetchSpotOrderInfo(context.Background(), "adfjashjgsag")
if err != nil {
t.Error(err)
}
}
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.GetDepositAddress(context.Background(), currency.USDT, "", "ETH")
if err != nil {
t.Error(err)
}
}
func TestWithdraw(t *testing.T) {
t.Parallel()
if areTestAPIKeysSet() && !canManipulateRealOrders {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
withdrawCryptoRequest := withdraw.Request{
Exchange: c.Name,
Amount: -1,
Currency: currency.BTC,
Description: "WITHDRAW IT ALL",
Crypto: withdraw.CryptoRequest{
Address: core.BitcoinDonationAddress,
},
}
_, err := c.WithdrawCryptocurrencyFunds(context.Background(), &withdrawCryptoRequest)
if !areTestAPIKeysSet() && err == nil {
t.Error("Expecting an error when no keys are set")
}
if areTestAPIKeysSet() && err != nil {
t.Errorf("Withdraw failed to be placed: %v", err)
}
}
func TestGetSpotOrderFills(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.GetSpotOrderFills(context.Background(), "1912131427156307968")
if err != nil {
t.Error(err)
}
}
func TestCancelSpotOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.CancelSpotOrder(context.Background(), "adfjashjgsag")
if err != nil {
t.Error(err)
}
}
func TestCancelSpotOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.CancelSpotOrders(context.Background(),
[]string{"578639816552972288", "578639902896914432"})
if err != nil {
t.Error(err)
}
}
func TestUpdateTicker(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.UpdateTicker(context.Background(), cp, asset.Spot)
if err != nil {
t.Error(err)
}
cp, err = currency.NewPairFromString(swapTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.UpdateTicker(context.Background(), cp, asset.PerpetualSwap)
if err != nil {
t.Error(err)
}
}
func TestUpdateTickers(t *testing.T) {
// TODO: fix Coinbene rate limiting that will allow to uncomment the next line
// and enable parallel testing
// t.Parallel()
err := c.UpdateTickers(context.Background(), asset.Spot)
if err != nil {
t.Error(err)
}
err = c.UpdateTickers(context.Background(), asset.PerpetualSwap)
if err != nil {
t.Error(err)
}
}
func TestGetAccountInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.UpdateAccountInfo(context.Background(), asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestUpdateOrderbook(t *testing.T) {
t.Parallel()
cp, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.UpdateOrderbook(context.Background(), cp, asset.Spot)
if err != nil {
t.Error(err)
}
cp, err = currency.NewPairFromString(swapTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.UpdateOrderbook(context.Background(), cp, asset.PerpetualSwap)
if err != nil {
t.Error(err)
}
}
func TestGetSwapTickers(t *testing.T) {
t.Parallel()
_, err := c.GetSwapTickers(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetSwapTicker(t *testing.T) {
t.Parallel()
_, err := c.GetSwapTicker(context.Background(), swapTestPair)
if err != nil {
t.Error(err)
}
}
func TestGetSwapOrderbook(t *testing.T) {
t.Parallel()
_, err := c.GetSwapOrderbook(context.Background(), swapTestPair, 100)
if err != nil {
t.Error(err)
}
}
func TestGetKlines(t *testing.T) {
t.Parallel()
p, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetKlines(context.Background(),
p.String(),
time.Now().Add(-time.Hour*1),
time.Now(),
"1")
if err != nil {
t.Fatal(err)
}
}
func TestGetSwapKlines(t *testing.T) {
t.Parallel()
p, err := currency.NewPairFromString(swapTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetSwapKlines(context.Background(),
p.String(),
time.Now().Add(-time.Hour*1),
time.Now(),
"1")
if err != nil {
t.Error(err)
}
}
func TestGetSwapTrades(t *testing.T) {
t.Parallel()
_, err := c.GetSwapTrades(context.Background(), swapTestPair, 10)
if err != nil {
t.Error(err)
}
}
func TestGetSwapInstruments(t *testing.T) {
t.Parallel()
_, err := c.GetSwapInstruments(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetSwapAccountInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.GetSwapAccountInfo(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetSwapPositions(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.GetSwapPositions(context.Background(), swapTestPair)
if err != nil {
t.Error(err)
}
}
func TestPlaceSwapOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.PlaceSwapOrder(context.Background(),
swapTestPair,
order.Buy.Lower(),
"limit",
"fixed",
"12345",
1,
1,
2)
if err != nil {
t.Error(err)
}
}
func TestCancelSwapOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.CancelSwapOrder(context.Background(), "1337")
if err != nil {
t.Error(err)
}
}
func TestGetOpenSwapOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.GetSwapOpenOrders(context.Background(), swapTestPair, 0, 0)
if err != nil {
t.Error(err)
}
}
func TestGetSwapOpenOrdersByPage(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.GetSwapOpenOrdersByPage(context.Background(), swapTestPair, 0)
if err != nil {
t.Error(err)
}
}
func TestGetSwapOrderInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.GetSwapOrderInfo(context.Background(), "1337")
if err != nil {
t.Error(err)
}
}
func TestGetSwapOrderHistory(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.GetSwapOrderHistory(context.Background(),
"", "", swapTestPair, 1, 10, "", "")
if err != nil {
t.Error(err)
}
}
func TestGetSwapOrderHistoryByOrderID(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.GetSwapOrderHistoryByOrderID(context.Background(),
"", "", swapTestPair, "", 0)
if err != nil {
t.Error(err)
}
}
func TestCancelSwapOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.CancelSwapOrders(context.Background(),
[]string{"578639816552972288", "578639902896914432"})
if err != nil {
t.Error(err)
}
}
func TestGetSwapOrderFills(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.GetSwapOrderFills(context.Background(),
swapTestPair, "5807143157122003", 580714315825905664)
if err != nil {
t.Error(err)
}
}
func TestGetSwapFundingRates(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
}
_, err := c.GetSwapFundingRates(context.Background(), 1, 2)
if err != nil {
t.Error(err)
}
}
func TestWsSubscribe(t *testing.T) {
pressXToJSON := []byte(`{"event":"subscribe","topic":"orderBook.BTCUSDT.10"}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestWsUnsubscribe(t *testing.T) {
pressXToJSON := []byte(`{"event":"unsubscribe","topic":"tradeList.BTCUSDT"}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestWsLogin(t *testing.T) {
pressXToJSON := []byte(`{"event":"login","success":true}`)
err := c.wsHandleData(pressXToJSON)
if err == nil {
t.Error("error cannot be nil as this will initiate an auth subscription")
}
pressXToJSON = []byte(`{"event":"login","success":false}`)
err = c.wsHandleData(pressXToJSON)
if err == nil {
t.Error("Expected error")
}
}
func TestWsOrderbook(t *testing.T) {
pressXToJSON := []byte(`{"topic":"spot/orderBook.BTCUSDT","action":"insert","data":[{"bids":[["0.00000015","174215.91"],["0.00000012","600000.00"],["0.00000010","10000.00"],["0.00000006","33333.00"],["0.00000004","50000.00"],["0.00000003","2000000.00"],["0.00000002","100000.00"],["0.00000001","1100000.00"]],"asks":[["0.00000262","5152.79"],["0.00000263","44626.00"],["0.00000340","2649.85"],["0.00000398","20056.93"],["0.00000400","1420385.54"],["0.00000790","8594.85"],["0.00000988","42380.97"],["0.00000997","43850.97"],["0.00001398","10541.59"],["0.00001400","3409.29"],["0.00002636","52.11"],["0.00002810","2543.66"],["0.00003200","1018.36"],["0.00004999","19.81"],["0.00005000","400.00"],["0.00005898","4060.56"],["0.00006498","3302.60"],["0.00006668","4060.56"],["0.00008000","400.00"]],"version":4915,"timestamp":1598529668288}]}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
pressXToJSON = []byte(`{"topic":"spot/orderBook.BTCUSDT","action":"update","data":[{"bids":[["2.983","8696"]],"asks":[["3.113","0"]],"version":34600866,"timestamp":1598587478738}]}`)
err = c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestWsTrade(t *testing.T) {
pressXToJSON := []byte(`{"data":[["0.00000050","s","37.00",1598500505000],["0.00000060","s","10.00",1598499782000],["0.00000066","s","1.00",1598499782000],["0.00000067","s","1.00",1598499782000],["0.00000068","s","1.00",1598499745000],["0.00000080","b","1.00",1598262728000],["0.00000089","b","5592.81",1597738441000],["0.00000072","b","1.00",1597693134000],["0.00000069","s","21739.13",1597378140000],["0.00000069","s","1.00",1597378140000],["0.00000074","b","1.00",1597354497000],["0.00000079","b","1.00",1597325675000],["0.00000082","b","1.00",1597162162000],["0.00000089","b","1.00",1597084892000],["0.00000073","b","109404.43",1597015827000],["0.00000070","b","1067.00",1597015827000],["0.00000070","b","1.00",1594732841000],["0.00000070","b","10.00",1592178569000],["0.00000065","b","194.76",1592178545000],["0.00000064","b","2.37",1592105641000],["0.00000064","b","3.00",1592087828000],["0.00000045","b","5.00",1592087828000],["0.00000045","b","5.00",1592004274000],["0.00000030","s","100.00",1591931268000],["0.00000020","b","138.12",1591928623000],["0.00000020","b","55027.66",1591928623000],["0.00000020","b","59880.11",1591572812000],["0.00000021","s","138.12",1590413750000],["0.00000021","s","5.37",1590413750000],["0.00000056","s","1.00",1589567228000],["0.00000065","b","1.00",1589567217000],["0.00000060","b","84890.64",1589407481000],["0.00000060","b","17.13",1589407433000],["0.00000060","b","9148.70",1589389270000],["0.00000059","b","9010.00",1589389159000],["0.00000055","b","3876.00",1589389098000],["0.00000055","b","30000.00",1588899981000],["0.00000055","b","5724.00",1588891192000],["0.00000050","b","400.00",1588891192000],["0.00000048","b","26874.64",1588891129000],["0.00000048","b","2.00",1588891129000],["0.00000049","b","7547.75",1585279296000],["0.00000049","b","12180.30",1584932828000],["0.00000049","b","8256.95",1584932828000],["0.00000053","b","500.42",1583351500000],["0.00000053","b","500.00",1583351484000],["0.00000053","b","400.00",1583351470000],["0.00000053","b","394.62",1583351455000],["0.00000053","b","1.99",1583343633000],["0.00000018","s","250.00",1583338813000]],"topic":"spot/tradeList.BTCUSDT","action":"insert"}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestWsTicker(t *testing.T) {
pressXToJSON := []byte(`{"topic": "spot/ticker.BTCUSDT","action":"insert","data": [{"symbol":"BTCUSDT","lastPrice":"23.3746","bestAskPrice":"23.3885","bestBidPrice":"23.3603","high24h":"23.5773","open24h":"22.1961","openPrice":"22.5546","low24h":"21.8077","volume24h":"3784807.9709","timestamp":1598587472634}]}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestWsKLine(t *testing.T) {
pressXToJSON := []byte(`{"topic": "spot/kline.BTCUSDT.1h","action":"insert","data": [{"t":1594990800,"o":1.1e-07,"h":1.1e-07,"l":1.1e-07,"c":1.1e-07,"v":0}]}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestWsUserAccount(t *testing.T) {
pressXToJSON := []byte(`{
"topic": "btc/user.account",
"data": [{
"asset": "BTC",
"availableBalance": "20.3859",
"frozenBalance": "0.7413",
"balance": "21.1272",
"timestamp": "2019-05-22T03:11:22.0Z"
}]
}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestWsUserPosition(t *testing.T) {
pressXToJSON := []byte(`{
"topic": "user.position",
"data": [{
"availableQuantity": "100",
"avgPrice": "7778.1",
"leverage": "20",
"liquidationPrice": "5441.0",
"markPrice": "8086.5",
"positionMargin": "0.0285",
"quantity": "507",
"realisedPnl": "0.0069",
"side": "long",
"symbol": "BTCUSDT",
"marginMode": "1",
"createTime": "2019-05-22T03:11:22.0Z"
}]
}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestWsUserOrder(t *testing.T) {
pressXToJSON := []byte(`{
"topic": "user.order",
"data": [{
"orderId": "580721369818955776",
"direction": "openLong",
"leverage": "20",
"symbol": "BTCUSDT",
"orderType": "limit",
"quantity": "7",
"orderPrice": "146.30",
"orderValue": "0.0010",
"fee": "0.0000",
"filledQuantity": "0",
"averagePrice": "0.00",
"orderTime": "2019-05-22T03:39:24.0Z",
"status": "new",
"lastFillQuantity": "0",
"lastFillPrice": "0",
"lastFillTime": ""
}]
}`)
err := c.wsHandleData(pressXToJSON)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricCandles(t *testing.T) {
currencyPair, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
startTime := time.Now().Add(-time.Hour * 24)
_, err = c.GetHistoricCandles(context.Background(),
currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour)
if err != nil {
t.Fatal(err)
}
currencyPairSwap, err := currency.NewPairFromString(swapTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetHistoricCandles(context.Background(),
currencyPairSwap, asset.PerpetualSwap, startTime, time.Now(), kline.OneHour)
if err != nil {
t.Fatal(err)
}
}
func TestGetHistoricCandlesExtended(t *testing.T) {
currencyPair, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
startTime := time.Now().Add(-time.Hour * 2)
_, err = c.GetHistoricCandlesExtended(context.Background(),
currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour)
if err != nil {
t.Fatal(err)
}
}
func Test_FormatExchangeKlineInterval(t *testing.T) {
testCases := []struct {
name string
interval kline.Interval
output string
}{
{
"OneMin",
kline.OneMin,
"1",
},
{
"OneHour",
kline.OneHour,
"60",
},
{
"OneDay",
kline.OneDay,
"D",
},
{
"OneWeek",
kline.OneWeek,
"W",
},
{
"OneMonth",
kline.OneMonth,
"M",
},
{
"AllOther",
kline.TwoWeek,
"",
},
}
for x := range testCases {
test := testCases[x]
t.Run(test.name, func(t *testing.T) {
ret := c.FormatExchangeKlineInterval(test.interval)
if ret != test.output {
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
}
})
}
}
func TestInferAssetFromTopic(t *testing.T) {
a := inferAssetFromTopic("spot/orderBook.BSVBTC")
if a != asset.Spot {
t.Error("expected spot")
}
a = inferAssetFromTopic("btc/orderBook.BSVBTC")
if a != asset.PerpetualSwap {
t.Error("expected PerpetualSwap")
}
a = inferAssetFromTopic("usdt/orderBook.BSVBTC")
if a != asset.PerpetualSwap {
t.Error("expected PerpetualSwap")
}
a = inferAssetFromTopic("orderBook.BSVBTC")
if a != asset.PerpetualSwap {
t.Error("expected PerpetualSwap")
}
a = inferAssetFromTopic("")
if a != asset.PerpetualSwap {
t.Error("expected PerpetualSwap")
}
}
func TestGetCurrencyFromWsTopic(t *testing.T) {
p, err := c.getCurrencyFromWsTopic(asset.Spot, "spot/orderbook.BTCUSDT")
if err != nil {
t.Error(err)
}
if p.Base.String() != "BTC" && p.Quote.String() != "USDT" {
t.Errorf("unexpected currency, wanted BTCUSD, received %v", p.String())
}
_, err = c.getCurrencyFromWsTopic(asset.Spot, "fake")
if err != nil && err.Error() != "no currency found in topic fake" {
t.Error(err)
}
_, err = c.getCurrencyFromWsTopic(asset.Spot, "hello.moto")
if err != nil && err.Error() != "currency moto not found in supplied pairs" {
t.Error(err)
}
_, err = c.getCurrencyFromWsTopic(asset.Spot, "spot/kline.GOM2USDT.1h")
if err != nil && err.Error() != "currency moto not found in enabled pairs" {
t.Error(err)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetRecentTrades(context.Background(), currencyPair, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(spotTestPair)
if err != nil {
t.Fatal(err)
}
_, err = c.GetHistoricTrades(context.Background(),
currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
if err != nil && err != common.ErrFunctionNotSupported {
t.Error(err)
}
}
func TestListDepositAddress(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("api keys not set")
}
_, err := c.ListDepositAddress(context.Background(), currency.USDT)
if err != nil {
t.Fatal(err)
}
}
func TestGetAvailableTransferCurrencies(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("api keys not set")
}
_, err := c.GetAvailableTransferChains(context.Background(), currency.USDT)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -1,439 +0,0 @@
package coinbene
import (
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
)
// Coinbene path vals
const (
APISpotPath uint8 = iota
APISwapPath
APICapitalPath
)
// TickerData stores ticker data
type TickerData struct {
Symbol string `json:"symbol"`
LatestPrice float64 `json:"latestPrice,string"`
BestBid float64 `json:"bestBid,string"`
BestAsk float64 `json:"bestAsk,string"`
DailyHigh float64 `json:"high24h,string"`
DailyLow float64 `json:"low24h,string"`
DailyVolume float64 `json:"volume24h,string"`
}
// OrderbookItem stores an individual orderbook item
type OrderbookItem struct {
Price float64
Amount float64
Count int64
}
// Orderbook stores the orderbook data
type Orderbook struct {
Bids []OrderbookItem
Asks []OrderbookItem
Symbol string
Time time.Time
}
// TradeItem stores a single trade
type TradeItem struct {
CurrencyPair string
Price float64
Volume float64
Direction string
TradeTime time.Time
}
// Trades stores trade data
type Trades []TradeItem
// PairData stores pair data
type PairData struct {
Symbol string `json:"symbol"`
BaseAsset string `json:"baseAsset"`
QuoteAsset string `json:"quoteAsset"`
PricePrecision int64 `json:"pricePrecision,string"`
AmountPrecision int64 `json:"amountPrecision,string"`
TakerFeeRate float64 `json:"takerFeeRate,string"`
MakerFeeRate float64 `json:"makerFeeRate,string"`
MinAmount float64 `json:"minAmount,string"`
Site string `json:"site"`
PriceFluctuation float64 `json:"priceFluctuation,string"`
}
// UserBalanceData stores user balance data
type UserBalanceData struct {
Asset string `json:"asset"`
Available float64 `json:"available,string"`
Reserved float64 `json:"reserved,string"`
Total float64 `json:"total,string"`
}
// PlaceOrderRequest places an order request
type PlaceOrderRequest struct {
Price float64
Quantity float64
Symbol string
Direction string
OrderType string
ClientID string
Notional int
}
// CancelOrdersResponse stores data for a cancelled order
type CancelOrdersResponse struct {
OrderID string `json:"orderId"`
Message string `json:"message"`
}
// OrderInfo stores order info
type OrderInfo struct {
OrderID string `json:"orderId"`
BaseAsset string `json:"baseAsset"`
QuoteAsset string `json:"quoteAsset"`
OrderType string `json:"orderDirection"`
Quantity float64 `json:"quntity,string"`
Amount float64 `json:"amout,string"`
FilledAmount float64 `json:"filledAmount"`
TakerRate float64 `json:"takerFeeRate,string"`
MakerRate float64 `json:"makerFeeRate,string"`
AvgPrice float64 `json:"avgPrice,string"`
OrderPrice float64 `json:"orderPrice,string"`
OrderStatus string `json:"orderStatus"`
OrderTime time.Time `json:"orderTime"`
TotalFee float64 `json:"totalFee"`
}
// OrderFills stores the fill info
type OrderFills struct {
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,string"`
Amount float64 `json:"amount,string"`
Fee float64 `json:"fee,string"`
Direction string `json:"direction"`
TradeTime time.Time `json:"tradeTime"`
FeeByConi float64 `json:"feeByConi,string"`
}
// OrdersInfo stores a collection of orders
type OrdersInfo []OrderInfo
// WsSub stores subscription data
type WsSub struct {
Operation string `json:"op"`
Arguments []string `json:"args"`
}
// WsTickerData stores websocket ticker data
type WsTickerData struct {
Symbol string `json:"symbol"`
LastPrice float64 `json:"lastPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
BestAskPrice float64 `json:"bestAskPrice,string"`
BestBidPrice float64 `json:"bestBidPrice,string"`
BestAskVolume float64 `json:"bestAskVolume,string"`
BestBidVolume float64 `json:"bestBidVolume,string"`
High24h float64 `json:"high24h,string"`
Low24h float64 `json:"low24h,string"`
Volume24h float64 `json:"volume24h,string"`
Timestamp int64 `json:"timestamp"`
}
// WsTicker stores websocket ticker
type WsTicker struct {
Topic string `json:"topic"`
Data []WsTickerData `json:"data"`
}
// WsTradeList stores websocket tradelist data
type WsTradeList struct {
Topic string `json:"topic"`
Data [][4]interface{} `json:"data"`
}
// WsTradeData stores trade data for websocket
type WsTradeData struct {
BestAskPrice float64 `json:"bestAskPrice,string"`
BestBidPrice float64 `json:"bestBidPrice,string"`
High24h float64 `json:"high24h,string"`
LastPrice float64 `json:"lastPrice,string"`
Low24h float64 `json:"low24h,string"`
Open24h float64 `json:"open24h,string"`
OpenPrice float64 `json:"openPrice,string"`
Symbol string `json:"symbol"`
Timestamp int64 `json:"timestamp"`
Volume24h float64 `json:"volume24h,string"`
}
// WsKline stores websocket kline data
type WsKline struct {
Topic string `json:"topic"`
Data []WsKLineData `json:"data"`
}
// WsKLineData holds OHLCV data
type WsKLineData struct {
Open float64 `json:"o"`
High float64 `json:"h"`
Low float64 `json:"l"`
Close float64 `json:"c"`
Volume float64 `json:"v"`
Timestamp int64 `json:"t"`
}
// WsUserData stores websocket user data
type WsUserData struct {
Asset string `json:"string"`
Available float64 `json:"availableBalance,string"`
Locked float64 `json:"frozenBalance,string"`
Total float64 `json:"balance,string"`
Timestamp time.Time `json:"timestamp"`
}
// WsUserInfo stores websocket user info
type WsUserInfo struct {
Topic string `json:"topic"`
Data []WsUserData `json:"data"`
}
// WsPositionData stores websocket info on user's position
type WsPositionData struct {
AvailableQuantity float64 `json:"availableQuantity,string"`
AveragePrice float64 `json:"avgPrice,string"`
Leverage int64 `json:"leverage,string"`
LiquidationPrice float64 `json:"liquidationPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
PositionMargin float64 `json:"positionMargin,string"`
Quantity float64 `json:"quantity,string"`
RealisedPNL float64 `json:"realisedPnl,string"`
Side string `json:"side"`
Symbol string `json:"symbol"`
MarginMode int64 `json:"marginMode,string"`
CreateTime time.Time `json:"createTime"`
}
// WsPosition stores websocket info on user's positions
type WsPosition struct {
Topic string `json:"topic"`
Data []WsPositionData `json:"data"`
}
// WsOrderbookData stores ws orderbook data
type WsOrderbookData struct {
Topic string `json:"topic"`
Action string `json:"action"`
Data []struct {
Bids [][2]string `json:"bids"`
Asks [][2]string `json:"asks"`
Version int64 `json:"version"`
Timestamp int64 `json:"timestamp"`
} `json:"data"`
}
// WsOrderData stores websocket user order data
type WsOrderData struct {
OrderID string `json:"orderId"`
Direction string `json:"direction"`
Leverage int64 `json:"leverage,string"`
Symbol string `json:"symbol"`
OrderType string `json:"orderType"`
Quantity float64 `json:"quantity,string"`
OrderPrice float64 `json:"orderPrice,string"`
OrderValue float64 `json:"orderValue,string"`
Fee float64 `json:"fee,string"`
FilledQuantity float64 `json:"filledQuantity,string"`
AveragePrice float64 `json:"averagePrice,string"`
OrderTime time.Time `json:"orderTime"`
Status string `json:"status"`
LastFillQuantity float64 `json:"lastFillQuantity,string"`
LastFillPrice float64 `json:"lastFillPrice,string"`
LastFillTime string `json:"lastFillTime"`
}
// WsUserOrders stores websocket user orders' data
type WsUserOrders struct {
Topic string `json:"topic"`
Data []WsOrderData `json:"data"`
}
// SwapTicker stores the swap ticker info
type SwapTicker struct {
LastPrice float64 `json:"lastPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
BestAskPrice float64 `json:"bestAskPrice,string"`
BestBidPrice float64 `json:"bestBidPrice,string"`
High24Hour float64 `json:"high24h,string"`
Low24Hour float64 `json:"low24h,string"`
Volume24Hour float64 `json:"volume24h,string"`
BestAskVolume float64 `json:"bestAskVolume,string"`
BestBidVolume float64 `json:"bestBidVolume,string"`
Turnover float64 `json:"turnover,string"`
Timestamp time.Time `json:"timeStamp"`
Change24Hour float64 `json:"chg24h,string"`
ChangeZeroHour float64 `json:"chg0h,string"`
}
// SwapTickers stores a map of swap tickers
type SwapTickers map[string]SwapTicker
// SwapKlineItem stores an individual kline data item
type SwapKlineItem struct {
Time time.Time
Open float64
Close float64
High float64
Low float64
Volume float64
Turnover float64
BuyVolume float64
BuyTurnover float64
}
// SwapKlines stores an array of kline data
type SwapKlines []SwapKlineItem
// Instrument stores an individual tradable instrument
type Instrument struct {
InstrumentID currency.Pair `json:"instrumentId"`
Multiplier float64 `json:"multiplier,string"`
MinimumAmount float64 `json:"minAmount,string"`
MaximumAmount float64 `json:"maxAmount,string"`
MinimumPriceChange float64 `json:"minPriceChange,string"`
PricePrecision int64 `json:"pricePrecision,string"`
}
// SwapTrade stores an individual trade
type SwapTrade struct {
Price float64
Side order.Side
Volume float64
Time time.Time
}
// SwapTrades stores an array of swap trades
type SwapTrades []SwapTrade
// SwapAccountInfo returns the swap account balance info
type SwapAccountInfo struct {
AvailableBalance float64 `json:"availableBalance,string"`
FrozenBalance float64 `json:"frozenBalance,string"`
MarginBalance float64 `json:"marginBalance,string"`
MarginRate float64 `json:"marginRate,string"`
Balance float64 `json:"balance,string"`
UnrealisedProfitAndLoss float64 `json:"unrealisedPnl,string"`
}
// SwapPosition stores a single swap position's data
type SwapPosition struct {
AvailableQuantity float64 `json:"availableQuantity,string"`
AveragePrice float64 `json:"averagePrice,string"`
CreateTime time.Time `json:"createTime"`
DeleveragePercentile int64 `json:"deleveragePercentile,string"`
Leverage int64 `json:"leverage,string"`
LiquidationPrice float64 `json:"liquidationPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
PositionMargin float64 `json:"positionMargin,string"`
PositionValue float64 `json:"positionValue,string"`
Quantity float64 `json:"quantity,string"`
RateOfReturn float64 `json:"roe,string"`
Side string `json:"side"`
Symbol string `json:"symbol"`
UnrealisedProfitAndLoss float64 `json:"UnrealisedPnl,string"`
}
// SwapPositions stores a collection of swap positions
type SwapPositions []SwapPosition
// SwapPlaceOrderResponse stores the response data for placing a swap order
type SwapPlaceOrderResponse struct {
OrderID string `json:"orderId"`
ClientID string `json:"clientId"`
}
// SwapOrder stores the swap order data
type SwapOrder struct {
OrderID string `json:"orderId"`
Direction string `json:"direction"`
Leverage int64 `json:"leverage,string"`
OrderType string `json:"orderType"`
Quantity float64 `json:"quantity,string"`
OrderPrice float64 `json:"orderPrice,string"`
OrderValue float64 `json:"orderValue,string"`
Fee float64 `json:"fee"`
FilledQuantity float64 `json:"filledQuantity,string"`
AveragePrice float64 `json:"averagePrice,string"`
OrderTime time.Time `json:"orderTime"`
Status string `json:"status"`
}
// SwapOrders stores a collection of swap orders
type SwapOrders []SwapOrder
// OrderCancellationResponse returns a list of cancel order status
type OrderCancellationResponse struct {
OrderID string `json:"orderId"`
Code int `json:"code,string"`
Message string `json:"message"`
}
// OrderPlacementResponse stores the order placement data
type OrderPlacementResponse OrderCancellationResponse
// SwapOrderFill stores a swap orders fill info
type SwapOrderFill struct {
Symbol string `json:"symbol"`
TradeTime time.Time `json:"tradeTime"`
TradeID int64 `json:"tradeId,string"`
OrderID int64 `json:"orderId,string"`
Price float64 `json:"price,string"`
Fee float64 `json:"fee,string"`
ExecType string `json:"execType"`
Side string `json:"side"`
Quantity float64 `json:"quantity,string"`
}
// SwapOrderFills stores a collection of swap order fills
type SwapOrderFills []SwapOrderFill
// SwapFundingRate stores a collection of funding rates
type SwapFundingRate struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
MarkPrice float64 `json:"markPrice,string"`
PositionValue float64 `json:"positionValue,string"`
Fee float64 `json:"fee,string"`
FeeRate float64 `json:"feeRate,string"`
Leverage int64 `json:"leverage"`
}
// CandleResponse stores returned kline data
type CandleResponse struct {
Code int64 `json:"code"`
Message string `json:"message"`
Data [][]interface{} `json:"data"`
}
// DepositAddress stores the deposit address data
type DepositAddress struct {
Asset string `json:"asset"`
Chain string `json:"chain"`
Address string `json:"address"`
AddressTag string `json:"addressTag"`
DepositLimit float64 `json:"depositLimit,string"`
BlockNumber uint8 `json:"blockNumber,string"`
}
// WithdrawResponse stores the withdrawal request response data
type WithdrawResponse struct {
ID string `json:"id"`
Amount float64 `json:"amount"`
Asset string `json:"asset"`
Address string `json:"address"`
Tag string `json:"tag"`
Chain string `json:"chain"`
}

View File

@@ -1,517 +0,0 @@
package coinbene
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
)
const (
wsContractURL = "wss://ws.coinbene.com/stream/ws"
event = "event"
topic = "topic"
swapChannelPrefix = "btc/"
spotChannelPrefix = "spot/"
)
// WsConnect connects to websocket
func (c *Coinbene) WsConnect() error {
if !c.Websocket.IsEnabled() || !c.IsEnabled() {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := c.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
c.Websocket.Wg.Add(1)
go c.wsReadData()
if c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
err = c.Login()
if err != nil {
c.Websocket.DataHandler <- err
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
}
return nil
}
// GenerateDefaultSubscriptions generates stuff
func (c *Coinbene) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
var channels = []string{"orderBook.%s.100", "tradeList.%s", "ticker.%s", "kline.%s.1h"}
var subscriptions []stream.ChannelSubscription
perpetualPairs, err := c.GetEnabledPairs(asset.PerpetualSwap)
if err != nil {
return nil, err
}
var spotPairs currency.Pairs
spotPairs, err = c.GetEnabledPairs(asset.Spot)
if err != nil {
return nil, err
}
for x := range channels {
for y := range perpetualPairs {
perpetualPairs[y].Delimiter = ""
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: swapChannelPrefix + fmt.Sprintf(channels[x], perpetualPairs[y]),
Currency: perpetualPairs[y],
Asset: asset.PerpetualSwap,
})
}
for z := range spotPairs {
spotPairs[z].Delimiter = ""
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: spotChannelPrefix + fmt.Sprintf(channels[x], spotPairs[z]),
Currency: spotPairs[z],
Asset: asset.Spot,
})
}
}
return subscriptions, nil
}
// GenerateAuthSubs generates auth subs
func (c *Coinbene) GenerateAuthSubs() ([]stream.ChannelSubscription, error) {
var subscriptions []stream.ChannelSubscription
var sub stream.ChannelSubscription
var userChannels = []string{"user.account", "user.position", "user.order"}
for z := range userChannels {
sub.Channel = userChannels[z]
subscriptions = append(subscriptions, sub)
}
return subscriptions, nil
}
// wsReadData receives and passes on websocket messages for processing
func (c *Coinbene) wsReadData() {
defer c.Websocket.Wg.Done()
for {
resp := c.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
return
}
err := c.wsHandleData(resp.Raw)
if err != nil {
c.Websocket.DataHandler <- err
}
}
}
func inferAssetFromTopic(topic string) asset.Item {
if strings.Contains(topic, "spot/") {
return asset.Spot
}
return asset.PerpetualSwap
}
func (c *Coinbene) wsHandleData(respRaw []byte) error {
if string(respRaw) == stream.Ping {
err := c.Websocket.Conn.SendRawMessage(websocket.TextMessage, []byte(stream.Pong))
if err != nil {
return err
}
return nil
}
var result map[string]interface{}
err := json.Unmarshal(respRaw, &result)
if err != nil {
return err
}
_, ok := result[event]
switch {
case ok && (result[event].(string) == "subscribe" || result[event].(string) == "unsubscribe"):
return nil
case ok && result[event].(string) == "error":
return fmt.Errorf("message: %s. code: %v", result["message"], result["code"])
}
if ok && strings.Contains(result[event].(string), "login") {
if result["success"].(bool) {
c.Websocket.SetCanUseAuthenticatedEndpoints(true)
var authsubs []stream.ChannelSubscription
authsubs, err = c.GenerateAuthSubs()
if err != nil {
return err
}
return c.Websocket.SubscribeToChannels(authsubs)
}
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
return fmt.Errorf("message: %s. code: %v", result["message"], result["code"])
}
assetType := inferAssetFromTopic(result[topic].(string))
var newPair currency.Pair
switch {
case strings.Contains(result[topic].(string), "ticker"):
var wsTicker WsTicker
err = json.Unmarshal(respRaw, &wsTicker)
if err != nil {
return err
}
newPair, err = c.getCurrencyFromWsTopic(assetType, wsTicker.Topic)
if err != nil {
return err
}
for x := range wsTicker.Data {
c.Websocket.DataHandler <- &ticker.Price{
Volume: wsTicker.Data[x].Volume24h,
Last: wsTicker.Data[x].LastPrice,
High: wsTicker.Data[x].High24h,
Low: wsTicker.Data[x].Low24h,
Bid: wsTicker.Data[x].BestBidPrice,
Ask: wsTicker.Data[x].BestAskPrice,
Pair: newPair,
ExchangeName: c.Name,
AssetType: assetType,
LastUpdated: time.Unix(wsTicker.Data[x].Timestamp, 0),
}
}
case strings.Contains(result[topic].(string), "tradeList"):
if !c.IsSaveTradeDataEnabled() {
return nil
}
var tradeList WsTradeList
err = json.Unmarshal(respRaw, &tradeList)
if err != nil {
return err
}
var trades []trade.Data
for i := range tradeList.Data {
var price, amount float64
t := time.Unix(int64(tradeList.Data[i][3].(float64))/1000, 0)
price, err = strconv.ParseFloat(tradeList.Data[i][0].(string), 64)
if err != nil {
return err
}
amount, err = strconv.ParseFloat(tradeList.Data[i][2].(string), 64)
if err != nil {
return err
}
var tSide = order.Buy
if tradeList.Data[i][1] == "s" {
tSide = order.Sell
}
newPair, err = c.getCurrencyFromWsTopic(assetType, tradeList.Topic)
if err != nil {
return err
}
trades = append(trades, trade.Data{
Timestamp: t,
Exchange: c.Name,
CurrencyPair: newPair,
AssetType: assetType,
Price: price,
Amount: amount,
Side: tSide,
})
}
return trade.AddTradesToBuffer(c.Name, trades...)
case strings.Contains(result[topic].(string), "orderBook"):
var orderBook WsOrderbookData
err = json.Unmarshal(respRaw, &orderBook)
if err != nil {
return err
}
if len(orderBook.Data) != 1 {
return errors.New("incomplete orderbook data has been received")
}
newPair, err = c.getCurrencyFromWsTopic(assetType, orderBook.Topic)
if err != nil {
return err
}
var amount, price float64
var asks, bids []orderbook.Item
for i := range orderBook.Data[0].Asks {
amount, err = strconv.ParseFloat(orderBook.Data[0].Asks[i][1], 64)
if err != nil {
return err
}
price, err = strconv.ParseFloat(orderBook.Data[0].Asks[i][0], 64)
if err != nil {
return err
}
asks = append(asks, orderbook.Item{
Amount: amount,
Price: price,
})
}
for j := range orderBook.Data[0].Bids {
price, err = strconv.ParseFloat(orderBook.Data[0].Bids[j][0], 64)
if err != nil {
return err
}
if price == 0 {
// Last level is coming back as a float with not enough decimal
// places e.g. ["0.000","1001.95"]],
// This needs to be filtered out as this can skew orderbook
// calculations
continue
}
amount, err = strconv.ParseFloat(orderBook.Data[0].Bids[j][1], 64)
if err != nil {
return err
}
bids = append(bids, orderbook.Item{
Amount: amount,
Price: price,
})
}
if orderBook.Action == "insert" {
var newOB orderbook.Base
newOB.Asks = asks
newOB.Bids = bids
newOB.Asset = assetType
newOB.Pair = newPair
newOB.Exchange = c.Name
newOB.LastUpdated = time.Unix(orderBook.Data[0].Timestamp, 0)
newOB.VerifyOrderbook = c.CanVerifyOrderbook
err = c.Websocket.Orderbook.LoadSnapshot(&newOB)
if err != nil {
return err
}
} else if orderBook.Action == "update" {
newOB := buffer.Update{
Asks: asks,
Bids: bids,
Asset: assetType,
Pair: newPair,
UpdateID: orderBook.Data[0].Version,
UpdateTime: time.Unix(orderBook.Data[0].Timestamp, 0),
}
err = c.Websocket.Orderbook.Update(&newOB)
if err != nil {
return err
}
}
case strings.Contains(result[topic].(string), "kline"):
var candleData WsKline
err = json.Unmarshal(respRaw, &candleData)
if err != nil {
return err
}
newPair, err = c.getCurrencyFromWsTopic(assetType, candleData.Topic)
if err != nil {
return err
}
for i := range candleData.Data {
c.Websocket.DataHandler <- stream.KlineData{
Pair: newPair,
AssetType: assetType,
Exchange: c.Name,
OpenPrice: candleData.Data[i].Open,
HighPrice: candleData.Data[i].High,
LowPrice: candleData.Data[i].Low,
ClosePrice: candleData.Data[i].Close,
Volume: candleData.Data[i].Volume,
Timestamp: time.Unix(candleData.Data[i].Timestamp, 0),
}
}
case strings.Contains(result[topic].(string), "user.account"):
var userInfo WsUserInfo
err = json.Unmarshal(respRaw, &userInfo)
if err != nil {
return err
}
c.Websocket.DataHandler <- userInfo
case strings.Contains(result[topic].(string), "user.position"):
var position WsPosition
err = json.Unmarshal(respRaw, &position)
if err != nil {
return err
}
c.Websocket.DataHandler <- position
case strings.Contains(result[topic].(string), "user.order"):
var orders WsUserOrders
err = json.Unmarshal(respRaw, &orders)
if err != nil {
return err
}
format, err := c.GetPairFormat(assetType, true)
if err != nil {
return err
}
pairs, err := c.GetEnabledPairs(assetType)
if err != nil {
return err
}
for i := range orders.Data {
oType, err := order.StringToOrderType(orders.Data[i].OrderType)
if err != nil {
c.Websocket.DataHandler <- order.ClassificationError{
Exchange: c.Name,
OrderID: orders.Data[i].OrderID,
Err: err,
}
}
oStatus, err := order.StringToOrderStatus(orders.Data[i].Status)
if err != nil {
c.Websocket.DataHandler <- order.ClassificationError{
Exchange: c.Name,
OrderID: orders.Data[i].OrderID,
Err: err,
}
}
newPair, err = currency.NewPairFromFormattedPairs(orders.Data[i].Symbol,
pairs,
format)
if err != nil {
return err
}
c.Websocket.DataHandler <- &order.Detail{
Price: orders.Data[i].OrderPrice,
Amount: orders.Data[i].Quantity,
ExecutedAmount: orders.Data[i].FilledQuantity,
RemainingAmount: orders.Data[i].Quantity - orders.Data[i].FilledQuantity,
Fee: orders.Data[i].Fee,
Exchange: c.Name,
ID: orders.Data[i].OrderID,
Type: oType,
Status: oStatus,
AssetType: assetType,
Date: orders.Data[i].OrderTime,
Leverage: float64(orders.Data[i].Leverage),
Pair: newPair,
}
}
default:
c.Websocket.DataHandler <- stream.UnhandledMessageWarning{
Message: c.Name + stream.UnhandledMessage + string(respRaw),
}
return nil
}
return nil
}
func (c *Coinbene) getCurrencyFromWsTopic(assetType asset.Item, channelTopic string) (cp currency.Pair, err error) {
var format currency.PairFormat
format, err = c.GetPairFormat(assetType, true)
if err != nil {
return cp, err
}
var pairs currency.Pairs
pairs, err = c.GetEnabledPairs(assetType)
if err != nil {
return cp, err
}
// channel topics are formatted as "spot/orderbook.BTCUSDT"
channelSplit := strings.Split(channelTopic, ".")
if len(channelSplit) == 1 {
return currency.Pair{}, errors.New("no currency found in topic " + channelTopic)
}
cp, err = currency.MatchPairsWithNoDelimiter(channelSplit[1], pairs, format)
if err != nil {
return cp, err
}
if !pairs.Contains(cp, true) {
return cp, fmt.Errorf("currency %s not found in enabled pairs", cp.String())
}
return cp, nil
}
// Subscribe sends a websocket message to receive data from the channel
func (c *Coinbene) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
if maxSubsPerHour := 240; len(channelsToSubscribe) > maxSubsPerHour {
return fmt.Errorf("channel subscriptions length %d exceeds coinbene's limit of %d, try reducing enabled pairs",
len(channelsToSubscribe),
maxSubsPerHour)
}
var sub WsSub
sub.Operation = "subscribe"
// enabling all currencies can lead to a message too large being sent
// and no subscriptions being made
chanLimit := 15
for i := range channelsToSubscribe {
if len(sub.Arguments) > chanLimit {
err := c.Websocket.Conn.SendJSONMessage(sub)
if err != nil {
return err
}
sub.Arguments = []string{}
}
sub.Arguments = append(sub.Arguments, channelsToSubscribe[i].Channel)
}
err := c.Websocket.Conn.SendJSONMessage(sub)
if err != nil {
return err
}
c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...)
return nil
}
// Unsubscribe sends a websocket message to receive data from the channel
func (c *Coinbene) Unsubscribe(channelToUnsubscribe []stream.ChannelSubscription) error {
var unsub WsSub
unsub.Operation = "unsubscribe"
// enabling all currencies can lead to a message too large being sent
// and no unsubscribes being made
chanLimit := 15
for i := range channelToUnsubscribe {
if len(unsub.Arguments) > chanLimit {
err := c.Websocket.Conn.SendJSONMessage(unsub)
if err != nil {
return err
}
unsub.Arguments = []string{}
}
unsub.Arguments = append(unsub.Arguments, channelToUnsubscribe[i].Channel)
}
err := c.Websocket.Conn.SendJSONMessage(unsub)
if err != nil {
return err
}
c.Websocket.RemoveSuccessfulUnsubscriptions(channelToUnsubscribe...)
return nil
}
// Login logs in
func (c *Coinbene) Login() error {
var sub WsSub
expTime := time.Now().Add(time.Minute * 10).Format("2006-01-02T15:04:05Z")
signMsg := expTime + http.MethodGet + "/login"
tempSign, err := crypto.GetHMAC(crypto.HashSHA256,
[]byte(signMsg),
[]byte(c.API.Credentials.Secret))
if err != nil {
return err
}
sign := crypto.HexEncodeToString(tempSign)
sub.Operation = "login"
sub.Arguments = []string{c.API.Credentials.Key, expTime, sign}
return c.Websocket.Conn.SendJSONMessage(sub)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,259 +0,0 @@
package coinbene
import (
"context"
"errors"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"golang.org/x/time/rate"
)
const (
// Contract rate limit time interval and request rates
contractRateInterval = time.Second * 2
orderbookContractReqRate = 20
tickersContractReqRate = 20
klineContractReqRate = 20
tradesContractReqRate = 20
contractInstrumentsReqRate = 20
contractAccountInfoContractReqRate = 10
positionInfoContractReqRate = 10
placeOrderContractReqRate = 20
cancelOrderContractReqRate = 20
getOpenOrdersContractReqRate = 5
openOrdersByPageContractReqRate = 5
getOrderInfoContractReqRate = 10
getClosedOrdersContractReqRate = 5
getClosedOrdersbyPageContractReqRate = 5
cancelMultipleOrdersContractReqRate = 5
getOrderFillsContractReqRate = 10
getFundingRatesContractReqRate = 10
// Spot rate limit time interval and request rates
spotRateInterval = time.Second
getPairsSpotReqRate = 2
getPairsInfoSpotReqRate = 3
getOrderbookSpotReqRate = 6
getTickerListSpotReqRate = 6
getSpecificTickerSpotReqRate = 6
getMarketTradesSpotReqRate = 3
// getKlineSpotReqRate = 1
// getExchangeRateSpotReqRate = 1
getAccountInfoSpotReqRate = 3
queryAccountAssetInfoSpotReqRate = 6
placeOrderSpotReqRate = 6
batchOrderSpotReqRate = 3
queryOpenOrdersSpotReqRate = 3
queryClosedOrdersSpotReqRate = 3
querySpecficOrderSpotReqRate = 6
queryTradeFillsSpotReqRate = 3
cancelOrderSpotReqRate = 6
cancelOrdersBatchSpotReqRate = 3
capitalDepositReqRate = 1
capitalWithdrawReqRate = 1
// Rate limit functionality
contractOrderbook request.EndpointLimit = iota
contractTickers
contractKline
contractTrades
contractInstruments
contractAccountInfo
contractPositionInfo
contractPlaceOrder
contractCancelOrder
contractGetOpenOrders
contractOpenOrdersByPage
contractGetOrderInfo
contractGetClosedOrders
contractGetClosedOrdersbyPage
contractCancelMultipleOrders
contractGetOrderFills
contractGetFundingRates
spotPairs
spotPairInfo
spotOrderbook
spotTickerList
spotSpecificTicker
spotMarketTrades
spotKline // Not implemented yet
spotExchangeRate // Not implemented yet
spotAccountInfo
spotAccountAssetInfo
spotPlaceOrder
spotBatchOrder
spotQueryOpenOrders
spotQueryClosedOrders
spotQuerySpecficOrder
spotQueryTradeFills
spotCancelOrder
spotCancelOrdersBatch
capitalDeposit
capitalWithdraw
)
// RateLimit implements the request.Limiter interface
type RateLimit struct {
ContractOrderbook *rate.Limiter
ContractTickers *rate.Limiter
ContractKline *rate.Limiter
ContractTrades *rate.Limiter
ContractInstruments *rate.Limiter
ContractAccountInfo *rate.Limiter
ContractPositionInfo *rate.Limiter
ContractPlaceOrder *rate.Limiter
ContractCancelOrder *rate.Limiter
ContractGetOpenOrders *rate.Limiter
ContractOpenOrdersByPage *rate.Limiter
ContractGetOrderInfo *rate.Limiter
ContractGetClosedOrders *rate.Limiter
ContractGetClosedOrdersbyPage *rate.Limiter
ContractCancelMultipleOrders *rate.Limiter
ContractGetOrderFills *rate.Limiter
ContractGetFundingRates *rate.Limiter
SpotPairs *rate.Limiter
SpotPairInfo *rate.Limiter
SpotOrderbook *rate.Limiter
SpotTickerList *rate.Limiter
SpotSpecificTicker *rate.Limiter
SpotMarketTrades *rate.Limiter
// spotKline // Not implemented yet
// spotExchangeRate // Not implemented yet
SpotAccountInfo *rate.Limiter
SpotAccountAssetInfo *rate.Limiter
SpotPlaceOrder *rate.Limiter
SpotBatchOrder *rate.Limiter
SpotQueryOpenOrders *rate.Limiter
SpotQueryClosedOrders *rate.Limiter
SpotQuerySpecficOrder *rate.Limiter
SpotQueryTradeFills *rate.Limiter
SpotCancelOrder *rate.Limiter
SpotCancelOrdersBatch *rate.Limiter
CapitalDeposit *rate.Limiter
CapitalWithdraw *rate.Limiter
}
// Limit limits outbound requests
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
switch f {
case contractOrderbook:
return r.ContractOrderbook.Wait(ctx)
case contractTickers:
return r.ContractTickers.Wait(ctx)
case contractKline:
return r.ContractKline.Wait(ctx)
case contractTrades:
return r.ContractTrades.Wait(ctx)
case contractInstruments:
return r.ContractInstruments.Wait(ctx)
case contractAccountInfo:
return r.ContractAccountInfo.Wait(ctx)
case contractPositionInfo:
return r.ContractPositionInfo.Wait(ctx)
case contractPlaceOrder:
return r.ContractPlaceOrder.Wait(ctx)
case contractCancelOrder:
return r.ContractCancelOrder.Wait(ctx)
case contractGetOpenOrders:
return r.ContractGetOpenOrders.Wait(ctx)
case contractOpenOrdersByPage:
return r.ContractOpenOrdersByPage.Wait(ctx)
case contractGetOrderInfo:
return r.ContractGetOrderInfo.Wait(ctx)
case contractGetClosedOrders:
return r.ContractGetClosedOrders.Wait(ctx)
case contractGetClosedOrdersbyPage:
return r.ContractGetClosedOrdersbyPage.Wait(ctx)
case contractCancelMultipleOrders:
return r.ContractCancelMultipleOrders.Wait(ctx)
case contractGetOrderFills:
return r.ContractGetOrderFills.Wait(ctx)
case contractGetFundingRates:
return r.ContractGetFundingRates.Wait(ctx)
case spotPairs:
return r.SpotPairs.Wait(ctx)
case spotPairInfo:
return r.SpotPairInfo.Wait(ctx)
case spotOrderbook:
return r.SpotOrderbook.Wait(ctx)
case spotTickerList:
return r.SpotTickerList.Wait(ctx)
case spotSpecificTicker:
return r.SpotSpecificTicker.Wait(ctx)
case spotMarketTrades:
return r.SpotMarketTrades.Wait(ctx)
case capitalDeposit:
return r.CapitalDeposit.Wait(ctx)
case capitalWithdraw:
return r.CapitalWithdraw.Wait(ctx)
// case spotKline: // Not implemented yet
// return r.SpotKline.Wait(ctx)
// case spotExchangeRate:
// return r.SpotExchangeRate.Wait(ctx)
case spotAccountInfo:
return r.SpotAccountInfo.Wait(ctx)
case spotAccountAssetInfo:
return r.SpotAccountAssetInfo.Wait(ctx)
case spotPlaceOrder:
return r.SpotPlaceOrder.Wait(ctx)
case spotBatchOrder:
return r.SpotBatchOrder.Wait(ctx)
case spotQueryOpenOrders:
return r.SpotQueryOpenOrders.Wait(ctx)
case spotQueryClosedOrders:
return r.SpotQueryClosedOrders.Wait(ctx)
case spotQuerySpecficOrder:
return r.SpotQuerySpecficOrder.Wait(ctx)
case spotQueryTradeFills:
return r.SpotQueryTradeFills.Wait(ctx)
case spotCancelOrder:
return r.SpotCancelOrder.Wait(ctx)
case spotCancelOrdersBatch:
return r.SpotCancelOrdersBatch.Wait(ctx)
default:
return errors.New("rate limit endpoint functionality not set")
}
}
// SetRateLimit returns the rate limit for the exchange
func SetRateLimit() *RateLimit {
return &RateLimit{
ContractOrderbook: request.NewRateLimit(contractRateInterval, orderbookContractReqRate),
ContractTickers: request.NewRateLimit(contractRateInterval, tickersContractReqRate),
ContractKline: request.NewRateLimit(contractRateInterval, klineContractReqRate),
ContractTrades: request.NewRateLimit(contractRateInterval, tradesContractReqRate),
ContractInstruments: request.NewRateLimit(contractRateInterval, contractInstrumentsReqRate),
ContractAccountInfo: request.NewRateLimit(contractRateInterval, contractAccountInfoContractReqRate),
ContractPositionInfo: request.NewRateLimit(contractRateInterval, positionInfoContractReqRate),
ContractPlaceOrder: request.NewRateLimit(contractRateInterval, placeOrderContractReqRate),
ContractCancelOrder: request.NewRateLimit(contractRateInterval, cancelOrderContractReqRate),
ContractGetOpenOrders: request.NewRateLimit(contractRateInterval, getOpenOrdersContractReqRate),
ContractOpenOrdersByPage: request.NewRateLimit(contractRateInterval, openOrdersByPageContractReqRate),
ContractGetOrderInfo: request.NewRateLimit(contractRateInterval, getOrderInfoContractReqRate),
ContractGetClosedOrders: request.NewRateLimit(contractRateInterval, getClosedOrdersContractReqRate),
ContractGetClosedOrdersbyPage: request.NewRateLimit(contractRateInterval, getClosedOrdersbyPageContractReqRate),
ContractCancelMultipleOrders: request.NewRateLimit(contractRateInterval, cancelMultipleOrdersContractReqRate),
ContractGetOrderFills: request.NewRateLimit(contractRateInterval, getOrderFillsContractReqRate),
ContractGetFundingRates: request.NewRateLimit(contractRateInterval, getFundingRatesContractReqRate),
SpotPairs: request.NewRateLimit(spotRateInterval, getPairsSpotReqRate),
SpotPairInfo: request.NewRateLimit(spotRateInterval, getPairsInfoSpotReqRate),
SpotOrderbook: request.NewRateLimit(spotRateInterval, getOrderbookSpotReqRate),
SpotTickerList: request.NewRateLimit(spotRateInterval, getTickerListSpotReqRate),
SpotSpecificTicker: request.NewRateLimit(spotRateInterval, getSpecificTickerSpotReqRate),
SpotMarketTrades: request.NewRateLimit(spotRateInterval, getMarketTradesSpotReqRate),
SpotAccountInfo: request.NewRateLimit(spotRateInterval, getAccountInfoSpotReqRate),
SpotAccountAssetInfo: request.NewRateLimit(spotRateInterval, queryAccountAssetInfoSpotReqRate),
SpotPlaceOrder: request.NewRateLimit(spotRateInterval, placeOrderSpotReqRate),
SpotBatchOrder: request.NewRateLimit(spotRateInterval, batchOrderSpotReqRate),
SpotQueryOpenOrders: request.NewRateLimit(spotRateInterval, queryOpenOrdersSpotReqRate),
SpotQueryClosedOrders: request.NewRateLimit(spotRateInterval, queryClosedOrdersSpotReqRate),
SpotQuerySpecficOrder: request.NewRateLimit(spotRateInterval, querySpecficOrderSpotReqRate),
SpotQueryTradeFills: request.NewRateLimit(spotRateInterval, queryTradeFillsSpotReqRate),
SpotCancelOrder: request.NewRateLimit(spotRateInterval, cancelOrderSpotReqRate),
SpotCancelOrdersBatch: request.NewRateLimit(spotRateInterval, cancelOrdersBatchSpotReqRate),
CapitalDeposit: request.NewRateLimit(spotRateInterval, capitalDepositReqRate),
CapitalWithdraw: request.NewRateLimit(spotRateInterval, capitalWithdrawReqRate),
}
}

View File

@@ -24,7 +24,6 @@ var Exchanges = []string{
"btc markets",
"btse",
"coinbasepro",
"coinbene",
"coinut",
"exmo",
"ftx",

View File

@@ -69,7 +69,6 @@ _b in this context is an `IBotExchange` implemented struct_
| Bittrex | Yes | Yes | No |
| BTCMarkets | Yes | Yes | No |
| BTSE | Yes | Yes | No |
| Coinbene | Yes | Yes | No |
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | No |
| Exmo | Yes | NA | No |

View File

@@ -2267,95 +2267,6 @@
}
]
},
{
"name": "Coinbene",
"enabled": true,
"verbose": false,
"httpTimeout": 15000000000,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"websocketTrafficTimeout": 30000000000,
"websocketOrderbookBufferLimit": 5,
"baseCurrencies": "USD",
"currencyPairs": {
"assetTypes": [
"spot",
"perpetualswap"
],
"pairs": {
"perpetualswap": {
"enabled": "BTC/SWAP",
"available": "YFI/SWAP,SUSHI/SWAP,TRX/SWAP,ETC/SWAP,XRP/SWAP,LTC/SWAP,EOS/SWAP,FIL/SWAP,UNI/SWAP,DOT/SWAP,LINK/SWAP,BSV/SWAP,BCH/SWAP,ETH/SWAP,BTC/SWAP",
"requestFormat": {
"uppercase": true,
"delimiter": "-"
},
"configFormat": {
"uppercase": true,
"delimiter": "/"
}
},
"spot": {
"enabled": "BTC/USDT,GOM2/USDT",
"available": "ABBC/BTC,ABBC/USDT,ABT/ETH,ABT/USDT,ABYSS/ETH,ACDC/BTC,ACDC/USDT,ADI/ETH,ADK/BTC,ADN/BTC,AE/BTC,AE/USDT,AIDOC/BTC,AION/BTC,AIPE/USDT,AIT/USDT,ALI/ETH,ALX/ETH,APL/ETH,ATX/BTC,BAAS/BTC,BABA/USDT,BAT/BTC,BCH/USDT,BETHER/ETH,BEZ/BTC,BGC/USDT,BKG/BTC,BNB/USDT,BNT/BTC,BOA/USDT,BSTN/ETH,BSV/USDT,BTC/USDT,BTNT/BTC,BTSC/BTC,BTT/USDT,BU/ETH,BVT/ETH,CAN/ETH,CCC/ETH,CCE/USDT,CEDEX/ETH,CENT/BTC,CFT/USDT,CMT/ETH,CMT/USDT,CNN/BTC,CNN/ETH,CNN/USDT,CONI/USDT,COSM/BTC,COSM/ETH,CPC/BTC,CREDO/ETH,CRN/BTC,CSCC/USDT,CS/ETH,CS/USDT,CTXC/ETH,CUST/USDT,CVC/BTC,CXP/BTC,DDAM/ETH,DDAM/USDT,DENT/BTC,DGD/BTC,DSCB/USDT,DTA/ETH,DUC/BTC,DVC/ETH,EBC/BTC,EBC/ETH,EBC/USDT,ECP/BTC,EDC/BTC,EDR/ETH,ELF/BTC,EMT/USDT,EOS/BTC,EOS/USDT,EQUAD/BTC,ETC/BTC,ETC/USDT,ETH/BTC,ETH/USDT,ETK/BTC,FAB/ETH,FCC/BTC,FND/ETH,FNKOS/ETH,FTN/BTC,FTN/USDT,FTT/BTC,FXT/ETH,GDC/BTC,GDC/ETH,GDC/USDT,GETX/ETH,GOM2/USDT,GRAM/USDT,GRN/BTC,GUSD/USDT,GVT/BTC,HAPPY/BTC,HDAC/BTC,HMB/USDT,HNB/USDT,HPT/ETH,HT/USDT,HUP/USDT,INCX/ETH,IOST/BTC,IOTE/USDT,ISR/ETH,IVY/ETH,JOB/BTC,KBC/BTC,KBC/USDT,KMD/BTC,KNT/ETH,KST/BTC,LAMB/USDT,LATX/BTC,LBK/BTC,LINK/BTC,LOOM/BTC,LTC/BTC,LTC/USDT,LUC/ETH,LUX/BTC,LVTC/ETH,MC/USDT,MIB/BTC,MINX/BTC,MINX/ETH,MOAC/USDT,MPL/BTC,MTC/BTC,MT/ETH,MTN/ETH,MT/USDT,MVL/ETH,MXM/ETH,MXM/USDT,MZG/USDT,NANO/BTC,NBAI/ETH,NEO/BTC,NEO/USDT,NFT/USDT,NOBS/BTC,NPXS/ETH,NPXS/USDT,NTY/ETH,ODC/USDT,OMG/BTC,OMX/ETH,OVC/ETH,OZX/ETH,PAT/ETH,PAX/USDT,PLAY/BTC,PMA/ETH,POLL/BTC,POLY/BTC,PPT/BTC,PSM/BTC,QKC/BTC,QTUM/BTC,QTUM/USDT,RBTC/BTC,RCOIN/BTC,RCOIN/USDT,REP/BTC,REV/BTC,RIF/BTC,RRW/USDT,SBT/USDT,SCC/BTC,SCO/BTC,SEN/BTC,SENC/ETH,SHE/BTC,SHVR/BTC,SIM/BTC,SKB/BTC,SKM/ETH,SKYM/USDT,SLT/ETH,SMARTUP/ETH,SMARTUP/USDT,SORO/USDT,SRCOIN/BTC,SRCOIN/ETH,STORJ/BTC,SWET/BTC,SWTC/USDT,TCT/BTC,TEMCO/USDT,TEN/BTC,TEN/ETH,TIB/BTC,TMTG/BTC,TOC/ETH,TOOS/USDT,TOSC/BTC,TRUE/ETH,TRX/BTC,TRX/USDT,TSL/BTC,UNI/USDT,UTNP/BTC,VBT/USDT,VEEN/BTC,VME/BTC,VME/ETH,VSC/ETH,VSF/BTC,W12/BTC,W12/ETH,WBL/BTC,WFX/BTC,XEM/BTC,XLM/BTC,XMCT/ETH,XMCT/USDT,XMR/BTC,XNK/ETH,XRP/BTC,XRP/USDT,XSR/USDT,YAP/BTC,YAP/USDT,YTA/USDT,ZAT/ETH,ZDC/BTC,ZEC/BTC,ZGC/BTC,ZRX/BTC",
"requestFormat": {
"uppercase": true,
"delimiter": "/"
},
"configFormat": {
"uppercase": true,
"delimiter": "/"
}
}
}
},
"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": {
"autoPairUpdates": true
},
"websocketAPI": true,
"websocketCapabilities": {}
},
"enabled": {
"autoPairUpdates": true,
"websocketAPI": false
}
},
"bankAccounts": [
{
"enabled": false,
"bankName": "",
"bankAddress": "",
"bankPostalCode": "",
"bankPostalCity": "",
"bankCountry": "",
"accountName": "",
"accountNumber": "",
"swiftCode": "",
"iban": "",
"supportedCurrencies": ""
}
]
},
{
"name": "FTX",
"enabled": true,

View File

@@ -8,7 +8,6 @@ bittrex,
btc markets,
btse,
coinbasepro,
coinbene,
coinut,
exmo,
ftx,
1 binance
8 btc markets
9 btse
10 coinbasepro
coinbene
11 coinut
12 exmo
13 ftx