diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index f192ce33..3f7eeb20 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -9,9 +9,9 @@ vadimzhukck | https://github.com/vadimzhukck
140am | https://github.com/140am
marcofranssen | https://github.com/marcofranssen
cranktakular | https://github.com/cranktakular
+MadCozBadd | https://github.com/MadCozBadd
leilaes | https://github.com/leilaes
crackcomm | https://github.com/crackcomm
-MadCozBadd | https://github.com/MadCozBadd
andreygrehov | https://github.com/andreygrehov
bretep | https://github.com/bretep
woshidama323 | https://github.com/woshidama323
@@ -30,8 +30,8 @@ frankzougc | https://github.com/frankzougc
starit | https://github.com/starit
Jimexist | https://github.com/Jimexist
lookfirst | https://github.com/lookfirst
-| mattkanwisher | https://github.com/mattkanwisher
-| mKurrels | https://github.com/mKurrels
-| m1kola | https://github.com/m1kola
-| cavapoo2 | https://github.com/cavapoo2
-| zeldrinn | https://github.com/zeldrinn
+mattkanwisher | https://github.com/mattkanwisher
+mKurrels | https://github.com/mKurrels
+m1kola | https://github.com/m1kola
+cavapoo2 | https://github.com/cavapoo2
+zeldrinn | https://github.com/zeldrinn
\ No newline at end of file
diff --git a/README.md b/README.md
index 86708b4c..76104bbc 100644
--- a/README.md
+++ b/README.md
@@ -130,19 +130,18 @@ Binaries will be published once the codebase reaches a stable condition.
|User|Github|Contribution Amount|
|--|--|--|
-| thrasher- | https://github.com/thrasher- | 543 |
-| shazbert | https://github.com/shazbert | 174 |
-| gloriousCode | https://github.com/gloriousCode | 154 |
+| thrasher- | https://github.com/thrasher- | 548 |
+| shazbert | https://github.com/shazbert | 176 |
+| gloriousCode | https://github.com/gloriousCode | 155 |
| xtda | https://github.com/xtda | 18 |
| ermalguni | https://github.com/ermalguni | 14 |
-| xtda | https://github.com/xtda | 11 |
| vadimzhukck | https://github.com/vadimzhukck | 10 |
| 140am | https://github.com/140am | 8 |
| marcofranssen | https://github.com/marcofranssen | 8 |
| cranktakular | https://github.com/cranktakular | 5 |
+| MadCozBadd | https://github.com/MadCozBadd | 3 |
| leilaes | https://github.com/leilaes | 3 |
| crackcomm | https://github.com/crackcomm | 3 |
-| MadCozBadd | https://github.com/MadCozBadd | 2 |
| andreygrehov | https://github.com/andreygrehov | 2 |
| bretep | https://github.com/bretep | 2 |
| woshidama323 | https://github.com/woshidama323 | 2 |
@@ -165,4 +164,6 @@ Binaries will be published once the codebase reaches a stable condition.
| mKurrels | https://github.com/mKurrels | 1 |
| m1kola | https://github.com/m1kola | 1 |
| cavapoo2 | https://github.com/cavapoo2 | 1 |
-| zeldrinn | https://github.com/zeldrinn | 1 |
\ No newline at end of file
+| zeldrinn | https://github.com/zeldrinn | 1 |
+
+
diff --git a/config/config_test.go b/config/config_test.go
index 24a48434..c5f59b18 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -13,7 +13,7 @@ import (
const (
// Default number of enabled exchanges. Modify this whenever an exchange is
// added or removed
- defaultEnabledExchanges = 27
+ defaultEnabledExchanges = 28
)
func TestGetCurrencyConfig(t *testing.T) {
diff --git a/config_example.json b/config_example.json
index 2cb350a9..17d4a32c 100644
--- a/config_example.json
+++ b/config_example.json
@@ -752,6 +752,53 @@
}
]
},
+ {
+ "name": "Coinbene",
+ "enabled": true,
+ "verbose": false,
+ "websocket": false,
+ "useSandbox": false,
+ "restPollingDelay": 10,
+ "httpTimeout": 15000000000,
+ "websocketResponseCheckTimeout": 30000000,
+ "websocketResponseMaxLimit": 7000000000,
+ "websocketOrderbookBufferLimit": 5,
+ "httpUserAgent": "",
+ "httpDebugging": false,
+ "authenticatedApiSupport": false,
+ "authenticatedWebsocketApiSupport": false,
+ "apiKey": "Key",
+ "apiSecret": "Secret",
+ "apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
+ "apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
+ "proxyAddress": "",
+ "websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API",
+ "clientId": "ClientID",
+ "availablePairs": "ABBC/BTC,ABT/ETH,ABT/USDT,ABYSS/ETH,ACDC/BTC,ACDC/USDT,ADI/ETH,ADK/BTC,ADN/BTC,AE/BTC,AE/USDT,AID/BTC,AIDOC/BTC,AION/BTC,AIPE/USDT,AIT/USDT,ALGO/USDT,ALI/ETH,ALX/ETH,APL/ETH,ATX/BTC,B2G/BTC,B91/USDT,BAAS/BTC,BAT/BTC,BCHABC/USDT,BCHSV/USDT,BEAUTY/ETH,BETHER/ETH,BEZ/BTC,BGC/USDT,BKG/BTC,BNT/BTC,BOA/USDT,BSTN/ETH,BTC/USDT,BTFM/USDT,BTNT/BTC,BTSC/BTC,BTT/USDT,BU/ETH,BVT/ETH,C3W/ETH,CAN/ETH,CCC/ETH,CCE/USDT,CC/USDT,CEDEX/ETH,CENT/BTC,CFT/USDT,CLO/BTC,CMT/ETH,CMT/USDT,CNN/BTC,CNN/ETH,CNN/USDT,CONI/USDT,COSM/BTC,COSM/ETH,COZP/BTC,CPC/BTC,CPMS/USDT,CREDO/ETH,CRN/BTC,CS/ETH,CS/USDT,CTXC/ETH,CUST/USDT,CVC/BTC,CXC/USDT,CXP/BTC,DCA/ETH,DCT/BTC,DENT/BTC,DGD/BTC,DOCK/ETH,DSCB/USDT,DTA/ETH,DUC/BTC,DVC/ETH,EBC/BTC,EBC/ETH,EBC/USDT,ECA/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,ETN/BTC,FAB/ETH,FACC/ETH,FCC/BTC,FDS/USDT,FND/ETH,FNKOS/ETH,FTN/BTC,FTN/USDT,FTT/BTC,FXT/ETH,GETX/ETH,GLDR/ETH,GMTK/ETH,GOM/USDT,GRAM/USDT,GRIN/BTC,GRN/BTC,GSTT/USDT,GUSD/USDT,GVT/BTC,HAPPY/BTC,HDAC/BTC,HMB/USDT,HNB/USDT,HPT/ETH,HUP/USDT,INCX/ETH,IOST/BTC,IOTE/USDT,ISR/BTC,ISR/ETH,IVY/ETH,JOB/BTC,KBC/BTC,KBC/USDT,KMD/BTC,KNT/ETH,KST/BTC,KUE/BTC,KUE/ETH,KUKY/BTC,LAMB/USDT,LATX/BTC,LBK/BTC,LINK/BTC,LOOM/BTC,LTC/BTC,LTC/USDT,LUC/ETH,LUX/BTC,LVTC/ETH,MDC/USDT,MGC/USDT,MIB/BTC,MINX/BTC,MINX/ETH,MOAC/USDT,MPL/BTC,MTC/BTC,MT/ETH,MTN/ETH,MT/USDT,MVL/ETH,MVPT/ETH,MWT/USDT,NANO/BTC,NBAI/ETH,NCASH/BTC,NEO/BTC,NEO/USDT,NOBS/BTC,NPXS/ETH,NPXS/USDT,NTY/ETH,ODC/USDT,OMG/BTC,OMX/ETH,OVC/ETH,OZX/ETH,PAL/ETH,PAT/ETH,PAX/USDT,PKX/BTC,PLAY/BTC,PMA/ETH,POLL/BTC,POLY/BTC,PPT/BTC,PSM/BTC,QKC/BTC,QTUM/BTC,QTUM/USDT,RBG/BTC,RBG/ETH,RBG/USDT,RBTC/BTC,RBZ/USDT,RCOIN/BTC,RCOIN/USDT,REP/BTC,REV/BTC,RIF/BTC,SALT/BTC,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,SMART/USDT,SORO/USDT,SRCOIN/BTC,SRCOIN/ETH,STORJ/BTC,STQ/BTC,SWET/BTC,SWTC/USDT,TCT/BTC,TEMCO/USDT,TEN/BTC,TEN/ETH,THM/ETH,TIB/BTC,TIMO/USDT,TMTG/BTC,TOC/ETH,TOSC/BTC,TRUE/ETH,TRX/BTC,TRX/USDT,TSL/BTC,TVB/USDT,UTNP/BTC,VBT/USDT,VEEN/BTC,VME/BTC,VME/ETH,VOLLAR/USDT,VSC/ETH,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,YTA/USDT,ZAT/ETH,ZDC/BTC,ZEC/BTC,ZGC/BTC,ZRX/BTC",
+ "enabledPairs": "BTC/USDT",
+ "baseCurrencies": "USD",
+ "assetTypes": "SPOT",
+ "supportsAutoPairUpdates": true,
+ "configCurrencyPairFormat": {
+ "uppercase": true,
+ "delimiter": "/"
+ },
+ "requestCurrencyPairFormat": {
+ "uppercase": true,
+ "delimiter": "/"
+ },
+ "bankAccounts": [
+ {
+ "bankName": "",
+ "bankAddress": "",
+ "accountName": "",
+ "accountNumber": "",
+ "swiftCode": "",
+ "iban": "",
+ "supportedCurrencies": ""
+ }
+ ]
+ },
{
"name": "GateIO",
"enabled": true,
diff --git a/exchange.go b/exchange.go
index 1d36f001..053e572f 100644
--- a/exchange.go
+++ b/exchange.go
@@ -18,6 +18,7 @@ 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/gateio"
@@ -151,6 +152,8 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
exch = new(btcmarkets.BTCMarkets)
case "btse":
exch = new(btse.BTSE)
+ case "coinbene":
+ exch = new(coinbene.Coinbene)
case "coinut":
exch = new(coinut.COINUT)
case "exmo":
diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go
index 1e173d1f..e1138267 100644
--- a/exchanges/bitfinex/bitfinex_websocket.go
+++ b/exchanges/bitfinex/bitfinex_websocket.go
@@ -164,9 +164,7 @@ func (b *Bitfinex) WsConnect() error {
func (b *Bitfinex) WsDataHandler() {
b.Websocket.Wg.Add(1)
- defer func() {
- b.Websocket.Wg.Done()
- }()
+ defer b.Websocket.Wg.Done()
for {
select {
diff --git a/exchanges/coinbene/README.md b/exchanges/coinbene/README.md
new file mode 100644
index 00000000..44f6ef31
--- /dev/null
+++ b/exchanges/coinbene/README.md
@@ -0,0 +1,141 @@
+# GoCryptoTrader package Coinbene
+
+
+
+
+[](https://travis-ci.org/thrasher-corp/gocryptotrader)
+[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
+[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/coinbene)
+[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
+[](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 progresss 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.GetTickerPrice()
+if err != nil {
+ // Handle error
+}
+
+// Fetches current orderbook information
+ob, err := c.GetOrderbookEx()
+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
+tradeID, err := c.Trade(...)
+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
+
+
+
+If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
+
+***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***
+
diff --git a/exchanges/coinbene/coinbene.go b/exchanges/coinbene/coinbene.go
new file mode 100644
index 00000000..b1909785
--- /dev/null
+++ b/exchanges/coinbene/coinbene.go
@@ -0,0 +1,395 @@
+package coinbene
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/thrasher-corp/gocryptotrader/common"
+ "github.com/thrasher-corp/gocryptotrader/config"
+ exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/request"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
+ log "github.com/thrasher-corp/gocryptotrader/logger"
+)
+
+// Coinbene is the overarching type across this package
+type Coinbene struct {
+ exchange.Base
+ WebsocketConn *wshandler.WebsocketConnection
+}
+
+const (
+ coinbeneAPIURL = "https://openapi-exchange.coinbene.com/api/exchange/"
+ coinbeneAuthPath = "/api/exchange/v2"
+ coinbeneAPIVersion = "v2"
+ buy = "buy"
+ sell = "sell"
+
+ // Public endpoints
+ coinbeneFetchTicker = "/market/ticker/one"
+ coinbeneFetchOrderBook = "/market/orderBook"
+ coinbeneGetTrades = "/market/trades"
+ coinbeneGetAllPairs = "/market/tradePair/list"
+ coinbenePairInfo = "/market/tradePair/one"
+
+ // Authenticated endpoints
+ coinbeneGetUserBalance = "/account/list"
+ coinbenePlaceOrder = "/order/place"
+ coinbeneOrderInfo = "/order/info"
+ coinbeneRemoveOrder = "/order/cancel"
+ coinbeneOpenOrders = "/order/openOrders"
+ coinbeneClosedOrders = "/order/closedOrders"
+
+ authRateLimit = 150
+ unauthRateLimit = 10
+)
+
+// SetDefaults sets the basic defaults for Coinbene
+func (c *Coinbene) SetDefaults() {
+ c.Name = "Coinbene"
+ c.Enabled = false
+ c.Verbose = false
+ c.RESTPollingDelay = 10
+ c.RequestCurrencyPairFormat.Delimiter = "/"
+ c.RequestCurrencyPairFormat.Uppercase = true
+ c.ConfigCurrencyPairFormat.Delimiter = "/"
+ c.ConfigCurrencyPairFormat.Uppercase = true
+ c.AssetTypes = []string{ticker.Spot}
+ c.SupportsAutoPairUpdating = true
+ c.SupportsRESTTickerBatching = false
+ c.Requester = request.New(c.Name,
+ request.NewRateLimit(time.Minute, authRateLimit),
+ request.NewRateLimit(time.Second, unauthRateLimit),
+ common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
+ c.APIUrlDefault = coinbeneAPIURL
+ c.APIUrl = c.APIUrlDefault
+ c.Websocket = wshandler.New()
+ c.WebsocketURL = coinbeneWsURL
+ c.Websocket.Functionality = wshandler.WebsocketTickerSupported |
+ wshandler.WebsocketTradeDataSupported |
+ wshandler.WebsocketKlineSupported |
+ wshandler.WebsocketAccountDataSupported |
+ wshandler.WebsocketOrderbookSupported |
+ wshandler.WebsocketSubscribeSupported |
+ wshandler.WebsocketUnsubscribeSupported |
+ wshandler.WebsocketAuthenticatedEndpointsSupported
+ c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
+ c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
+ c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
+}
+
+// Setup takes in the supplied exchange configuration details and sets params
+func (c *Coinbene) Setup(exch *config.ExchangeConfig) {
+ if !exch.Enabled {
+ c.SetEnabled(false)
+ } else {
+ c.Enabled = true
+ c.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
+ c.AuthenticatedWebsocketAPISupport = exch.AuthenticatedWebsocketAPISupport
+ c.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
+ c.SetHTTPClientTimeout(exch.HTTPTimeout)
+ c.SetHTTPClientUserAgent(exch.HTTPUserAgent)
+ c.RESTPollingDelay = exch.RESTPollingDelay
+ c.Verbose = exch.Verbose
+ c.Websocket.SetWsStatusAndConnection(exch.Websocket)
+ c.BaseCurrencies = exch.BaseCurrencies
+ c.AvailablePairs = exch.AvailablePairs
+ c.EnabledPairs = exch.EnabledPairs
+ err := c.SetCurrencyPairFormat()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = c.SetAssetTypes()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = c.SetAutoPairDefaults()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = c.SetAPIURL(exch)
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = c.SetClientProxyAddress(exch.ProxyAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = c.Websocket.Setup(c.WsConnect,
+ c.Subscribe,
+ c.Unsubscribe,
+ exch.Name,
+ exch.Websocket,
+ exch.Verbose,
+ coinbeneWsURL,
+ exch.WebsocketURL,
+ exch.AuthenticatedWebsocketAPISupport)
+ if err != nil {
+ log.Fatal(err)
+ }
+ c.WebsocketConn = &wshandler.WebsocketConnection{
+ ExchangeName: c.Name,
+ URL: c.Websocket.GetWebsocketURL(),
+ ProxyURL: c.Websocket.GetProxyAddress(),
+ Verbose: c.Verbose,
+ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
+ ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
+ }
+ c.Websocket.Orderbook.Setup(
+ exch.WebsocketOrderbookBufferLimit,
+ true,
+ true,
+ false,
+ false,
+ exch.Name)
+ }
+}
+
+// FetchTicker gets and stores ticker data for a currency pair
+func (c *Coinbene) FetchTicker(symbol string) (TickerResponse, error) {
+ var t TickerResponse
+ params := url.Values{}
+ params.Set("symbol", symbol)
+ path := common.EncodeURLValues(c.APIUrl+coinbeneAPIVersion+coinbeneFetchTicker, params)
+ return t, c.SendHTTPRequest(path, &t)
+}
+
+// FetchOrderbooks gets and stores orderbook data for given pair
+func (c *Coinbene) FetchOrderbooks(symbol string, size int64) (OrderbookResponse, error) {
+ var o OrderbookResponse
+ params := url.Values{}
+ params.Set("symbol", symbol)
+ params.Set("depth", strconv.FormatInt(size, 10))
+ path := common.EncodeURLValues(c.APIUrl+coinbeneAPIVersion+coinbeneFetchOrderBook, params)
+ return o, c.SendHTTPRequest(path, &o)
+}
+
+// GetTrades gets recent trades from the exchange
+func (c *Coinbene) GetTrades(symbol string) (TradeResponse, error) {
+ var t TradeResponse
+ params := url.Values{}
+ params.Set("symbol", symbol)
+ path := common.EncodeURLValues(c.APIUrl+coinbeneAPIVersion+coinbeneGetTrades, params)
+ return t, c.SendHTTPRequest(path, &t)
+}
+
+// GetPairInfo gets info about a single pair
+func (c *Coinbene) GetPairInfo(symbol string) (PairResponse, error) {
+ var resp PairResponse
+ params := url.Values{}
+ params.Set("symbol", symbol)
+ path := common.EncodeURLValues(c.APIUrl+coinbeneAPIVersion+coinbenePairInfo, params)
+ return resp, c.SendHTTPRequest(path, &resp)
+}
+
+// GetAllPairs gets all pairs on the exchange
+func (c *Coinbene) GetAllPairs() (AllPairResponse, error) {
+ var a AllPairResponse
+ path := c.APIUrl + coinbeneAPIVersion + coinbeneGetAllPairs
+ return a, c.SendHTTPRequest(path, &a)
+}
+
+// GetUserBalance gets user balanace info
+func (c *Coinbene) GetUserBalance() (UserBalanceResponse, error) {
+ var resp UserBalanceResponse
+ path := c.APIUrl + coinbeneAPIVersion + coinbeneGetUserBalance
+ err := c.SendAuthHTTPRequest(http.MethodGet, path, coinbeneGetUserBalance, nil, &resp)
+ if err != nil {
+ return resp, err
+ }
+ if resp.Code != 200 {
+ return resp, fmt.Errorf(resp.Message)
+ }
+ return resp, nil
+}
+
+// PlaceOrder creates an order
+func (c *Coinbene) PlaceOrder(price, quantity float64, symbol, direction, clientID string) (PlaceOrderResponse, error) {
+ var resp PlaceOrderResponse
+ path := c.APIUrl + coinbeneAPIVersion + coinbenePlaceOrder
+ params := url.Values{}
+ params.Set("symbol", symbol)
+ switch direction {
+ case sell:
+ params.Set("direction", "2")
+ case buy:
+ params.Set("direction", "1")
+ default:
+ return resp,
+ fmt.Errorf("passed in direction %s is invalid must be 'buy' or 'sell'",
+ direction)
+ }
+
+ params.Set("price", strconv.FormatFloat(price, 'f', -1, 64))
+ params.Set("quantity", strconv.FormatFloat(quantity, 'f', -1, 64))
+ params.Set("clientId", clientID)
+ err := c.SendAuthHTTPRequest(http.MethodPost,
+ path,
+ coinbenePlaceOrder,
+ params,
+ &resp)
+ if err != nil {
+ return resp, err
+ }
+ if resp.Code != 200 {
+ return resp, fmt.Errorf(resp.Message)
+ }
+ return resp, nil
+}
+
+// FetchOrderInfo gets order info
+func (c *Coinbene) FetchOrderInfo(orderID string) (OrderInfoResponse, error) {
+ var resp OrderInfoResponse
+ params := url.Values{}
+ params.Set("orderId", orderID)
+ path := c.APIUrl + coinbeneAPIVersion + coinbeneOrderInfo
+ err := c.SendAuthHTTPRequest(http.MethodGet, path, coinbeneOrderInfo, params, &resp)
+ if err != nil {
+ return resp, err
+ }
+ if resp.Code != 200 {
+ return resp, fmt.Errorf(resp.Message)
+ }
+ if resp.Order.OrderID != orderID {
+ return resp, fmt.Errorf("%s orderID doesn't match the returned orderID %s",
+ orderID, resp.Order.OrderID)
+ }
+ return resp, nil
+}
+
+// RemoveOrder removes a given order
+func (c *Coinbene) RemoveOrder(orderID string) (RemoveOrderResponse, error) {
+ var resp RemoveOrderResponse
+ params := url.Values{}
+ params.Set("orderId", orderID)
+ path := c.APIUrl + coinbeneAPIVersion + coinbeneRemoveOrder
+ err := c.SendAuthHTTPRequest(http.MethodPost, path, coinbeneRemoveOrder, params, &resp)
+ if err != nil {
+ return resp, err
+ }
+ if resp.Code != 200 {
+ return resp, fmt.Errorf(resp.Message)
+ }
+ return resp, nil
+}
+
+// FetchOpenOrders finds open orders
+func (c *Coinbene) FetchOpenOrders(symbol string) (OpenOrderResponse, error) {
+ var resp OpenOrderResponse
+ params := url.Values{}
+ params.Set("symbol", symbol)
+ path := c.APIUrl + coinbeneAPIVersion + coinbeneOpenOrders
+ for i := int64(1); ; i++ {
+ var temp OpenOrderResponse
+ params.Set("pageNum", strconv.FormatInt(i, 10))
+ err := c.SendAuthHTTPRequest(http.MethodGet, path, coinbeneOpenOrders, params, &temp)
+ if err != nil {
+ return resp, err
+ }
+ if temp.Code != 200 {
+ return resp, fmt.Errorf(temp.Message)
+ }
+ for j := range temp.OpenOrders {
+ resp.OpenOrders = append(resp.OpenOrders, temp.OpenOrders[j])
+ }
+
+ if len(temp.OpenOrders) != 20 {
+ break
+ }
+ }
+ return resp, nil
+}
+
+// FetchClosedOrders finds open orders
+func (c *Coinbene) FetchClosedOrders(symbol, latestID string) (ClosedOrderResponse, error) {
+ var resp ClosedOrderResponse
+ params := url.Values{}
+ params.Set("symbol", symbol)
+ params.Set("latestOrderId", latestID)
+ path := c.APIUrl + coinbeneAPIVersion + coinbeneClosedOrders
+ for i := int64(1); ; i++ {
+ var temp ClosedOrderResponse
+ params.Set("pageNum", strconv.FormatInt(i, 10))
+ err := c.SendAuthHTTPRequest(http.MethodGet, path, coinbeneClosedOrders, params, &temp)
+ if err != nil {
+ return resp, err
+ }
+ if temp.Code != 200 {
+ return resp, fmt.Errorf(temp.Message)
+ }
+ for j := range temp.Data {
+ resp.Data = append(resp.Data, temp.Data[j])
+ }
+ if len(temp.Data) != 20 {
+ break
+ }
+ }
+ return resp, nil
+}
+
+// SendHTTPRequest sends an unauthenticated HTTP request
+func (c *Coinbene) SendHTTPRequest(path string, result interface{}) error {
+ return c.SendPayload(http.MethodGet,
+ path,
+ nil,
+ nil,
+ &result,
+ false,
+ false,
+ c.Verbose,
+ c.HTTPDebugging,
+ c.HTTPRecording)
+}
+
+// SendAuthHTTPRequest sends an authenticated HTTP request
+func (c *Coinbene) SendAuthHTTPRequest(method, path, epPath string, params url.Values, result interface{}) error {
+ if params == nil {
+ params = url.Values{}
+ }
+ timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.999Z")
+ var finalBody io.Reader
+ var preSign string
+ switch {
+ case len(params) != 0 && method == http.MethodGet:
+ preSign = fmt.Sprintf("%s%s%s%s?%s", timestamp, method, coinbeneAuthPath, epPath, params.Encode())
+ path = common.EncodeURLValues(path, params)
+ case len(params) != 0:
+ m := make(map[string]string)
+ for k, v := range params {
+ m[k] = strings.Join(v, "")
+ }
+ tempBody, err := json.Marshal(m)
+ if err != nil {
+ return err
+ }
+ finalBody = bytes.NewBufferString(string(tempBody))
+ preSign = timestamp + method + coinbeneAuthPath + epPath + string(tempBody)
+ case len(params) == 0:
+ preSign = timestamp + method + coinbeneAuthPath + epPath
+ }
+ tempSign := common.GetHMAC(common.HashSHA256, []byte(preSign), []byte(c.APISecret))
+ headers := make(map[string]string)
+ headers["Content-Type"] = "application/json"
+ headers["ACCESS-KEY"] = c.APIKey
+ headers["ACCESS-SIGN"] = common.HexEncodeToString(tempSign)
+ headers["ACCESS-TIMESTAMP"] = timestamp
+ return c.SendPayload(method,
+ path,
+ headers,
+ finalBody,
+ &result,
+ true,
+ false,
+ c.Verbose,
+ c.HTTPDebugging,
+ c.HTTPRecording)
+}
diff --git a/exchanges/coinbene/coinbene_test.go b/exchanges/coinbene/coinbene_test.go
new file mode 100644
index 00000000..34e38a98
--- /dev/null
+++ b/exchanges/coinbene/coinbene_test.go
@@ -0,0 +1,184 @@
+package coinbene
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/thrasher-corp/gocryptotrader/config"
+ "github.com/thrasher-corp/gocryptotrader/currency"
+)
+
+// Please supply your own keys here for due diligence testing
+const (
+ testAPIKey = ""
+ testAPISecret = ""
+ canManipulateRealOrders = false
+
+ btcusdt = "BTC/USDT"
+)
+
+var c Coinbene
+
+func TestMain(m *testing.M) {
+ c.SetDefaults()
+ cfg := config.GetConfig()
+ err := cfg.LoadConfig("../../testdata/configtest.json")
+ if err != nil {
+ log.Fatalf("Test Failed - Coinbene Setup() init error:, %v", err)
+ }
+ coinbeneConfig, err := cfg.GetExchangeConfig("Coinbene")
+ if err != nil {
+ log.Fatalf("Test Failed - Coinbene Setup() init error: %v", err)
+ }
+ coinbeneConfig.Websocket = true
+ coinbeneConfig.AuthenticatedAPISupport = true
+ coinbeneConfig.APISecret = testAPISecret
+ coinbeneConfig.APIKey = testAPIKey
+ c.Setup(&coinbeneConfig)
+
+ os.Exit(m.Run())
+}
+
+func areTestAPIKeysSet() bool {
+ if c.APIKey != "" && c.APIKey != "Key" &&
+ c.APISecret != "" && c.APISecret != "Secret" {
+ return true
+ }
+ return false
+}
+
+func TestFetchTicker(t *testing.T) {
+ t.Parallel()
+ _, err := c.FetchTicker(btcusdt)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestFetchOrderbooks(t *testing.T) {
+ t.Parallel()
+ _, err := c.FetchOrderbooks(btcusdt, 100)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestGetTrades(t *testing.T) {
+ t.Parallel()
+ _, err := c.GetTrades(btcusdt)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestGetAllPairs(t *testing.T) {
+ t.Parallel()
+ _, err := c.GetAllPairs()
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestGetPairInfo(t *testing.T) {
+ t.Parallel()
+ _, err := c.GetPairInfo(btcusdt)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestGetUserBalance(t *testing.T) {
+ t.Parallel()
+ if !areTestAPIKeysSet() {
+ t.Skip("API keys required but not set, skipping test")
+ }
+ _, err := c.GetUserBalance()
+ 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.PlaceOrder(140, 1, btcusdt, "buy", "")
+ 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.FetchOrderInfo("adfjashjgsag")
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestRemoveOrder(t *testing.T) {
+ t.Parallel()
+ if !areTestAPIKeysSet() || !canManipulateRealOrders {
+ t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
+ }
+ _, err := c.RemoveOrder("adfjashjgsag")
+ 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.FetchOpenOrders(btcusdt)
+ 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(btcusdt, "")
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestUpdateTicker(t *testing.T) {
+ t.Parallel()
+ cp := currency.NewPairWithDelimiter("BTC", "USDT", "/")
+ _, err := c.UpdateTicker(cp, "spot")
+ 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.GetAccountInfo()
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestUpdateOrderbook(t *testing.T) {
+ t.Parallel()
+ cp := currency.NewPairWithDelimiter("BTC", "USDT", "/")
+ _, err := c.UpdateOrderbook(cp, "spot")
+ if err != nil {
+ t.Error(err)
+ }
+}
diff --git a/exchanges/coinbene/coinbene_types.go b/exchanges/coinbene/coinbene_types.go
new file mode 100644
index 00000000..04dc583c
--- /dev/null
+++ b/exchanges/coinbene/coinbene_types.go
@@ -0,0 +1,248 @@
+package coinbene
+
+// 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"`
+}
+
+// TickerResponse stores ticker response data
+type TickerResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ TickerData `json:"data"`
+}
+
+// Orderbook stores orderbook info
+type Orderbook struct {
+ Asks [][]string `json:"asks"`
+ Bids [][]string `json:"bids"`
+}
+
+// OrderbookResponse stores data from fetched orderbooks
+type OrderbookResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Orderbook `json:"data"`
+}
+
+// TradeResponse stores trade data
+type TradeResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Trades [][]string `json:"data"`
+}
+
+// AllPairData stores pair data
+type AllPairData 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"`
+}
+
+// AllPairResponse stores data for all pairs enabled on exchange
+type AllPairResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Data []AllPairData `json:"data"`
+}
+
+// PairResponse stores data for a single queried pair
+type PairResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Data AllPairData `json:"data"`
+}
+
+// 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"`
+}
+
+// UserBalanceResponse stores user balance data
+type UserBalanceResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Data []UserBalanceData `json:"data"`
+}
+
+// PlaceOrderResponse stores data for a placed order
+type PlaceOrderResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Status string `json:"status"`
+ Timestamp int64 `json:"timestamp"`
+ OrderID string `json:"orderid"`
+}
+
+// OrderInfoData stores order info
+type OrderInfoData 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:"makerRate,string"`
+ AvgPrice float64 `json:"avgPrice,string"`
+ OrderPrice float64 `json:"orderPrice,string"`
+ OrderStatus string `json:"orderStatus"`
+ OrderTime string `json:"orderTime"`
+ TotalFee float64 `json:"totalFee"`
+}
+
+// OrderInfoResponse stores orderinfo data
+type OrderInfoResponse struct {
+ Order OrderInfoData `json:"data"`
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+}
+
+// RemoveOrderResponse stores data for the remove request
+type RemoveOrderResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ OrderID string `json:"data"`
+}
+
+// OpenOrderResponse stores data for open orders
+type OpenOrderResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ OpenOrders []OrderInfoData `json:"data"`
+}
+
+// ClosedOrderResponse stores data for closed orders
+type ClosedOrderResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Data []OrderInfoData `json:"data"`
+}
+
+// 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:"volume,string"`
+ Timestamp string `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 [][]string `json:"data"`
+}
+
+// WsOrderbook stores websocket orderbook data
+type WsOrderbook struct {
+ Topic string `json:"topic"`
+ Action string `json:"action"`
+ Data []Orderbook `json:"data"`
+ Version int64 `json:"version,string"`
+ Timestamp string `json:"timestamp"`
+}
+
+// WsKline stores websocket kline data
+type WsKline struct {
+ Topic string `json:"topic"`
+ Data [][]interface{} `json:"data"`
+}
+
+// WsUserData stores websocket user data
+type WsUserData struct {
+ Asset string `json:"string"`
+ Available float64 `json:"availableBalance"`
+ Locked float64 `json:"frozenBalance"`
+ Total float64 `json:"balance"`
+ Timestamp string `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"`
+ AveragePrice float64 `json:"avgPrice"`
+ Leverage float64 `json:"leverage"`
+ LiquidationPrice float64 `json:"liquidationPrice"`
+ MarkPrice float64 `json:"markPrice"`
+ PositionMargin float64 `json:"positionMargin"`
+ Quantity float64 `json:"quantity"`
+ RealisedPNL float64 `json:"realisedPnl"`
+ Side string `json:"side"`
+ Symbol string `json:"symbol"`
+ MarginMode int64 `json:"marginMode"`
+ CreateTime string `json:"createTime"`
+}
+
+// WsPosition stores websocket info on user's positions
+type WsPosition struct {
+ Topic string `json:"topic"`
+ Data []WsPositionData `json:"data"`
+}
+
+// WsOrderData stores websocket user order data
+type WsOrderData struct {
+ OrderID string `json:"orderId"`
+ Direction string `json:"direction"`
+ Leverage float64 `json:"leverage"`
+ Symbol string `json:"symbol"`
+ OrderType string `json:"orderType"`
+ Quantity float64 `json:"quantity"`
+ OrderPrice float64 `json:"orderPrice"`
+ OrderValue float64 `json:"orderValue"`
+ Fee float64 `json:"fee"`
+ FilledQuantity float64 `json:"filledQuantity"`
+ AveragePrice float64 `json:"averagePrice"`
+ OrderTime string `json:"orderTime"`
+ Status string `json:"status"`
+ LastFillQuantity float64 `json:"lastFillQuantity"`
+ LastFillPrice float64 `json:"lastFillPrice"`
+ LastFillTime string `json:"lastFillTime"`
+}
+
+// WsUserOrders stores websocket user orders' data
+type WsUserOrders struct {
+ Topic string `json:"topic"`
+ Data []WsOrderData `json:"data"`
+}
diff --git a/exchanges/coinbene/coinbene_websocket.go b/exchanges/coinbene/coinbene_websocket.go
new file mode 100644
index 00000000..7f83df30
--- /dev/null
+++ b/exchanges/coinbene/coinbene_websocket.go
@@ -0,0 +1,336 @@
+package coinbene
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gorilla/websocket"
+ "github.com/thrasher-corp/gocryptotrader/common"
+ "github.com/thrasher-corp/gocryptotrader/currency"
+ exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
+)
+
+const (
+ coinbeneWsURL = "wss://ws-contract.coinbene.vip/openapi/ws"
+ event = "event"
+ topic = "topic"
+)
+
+// WsConnect connects to websocket
+func (c *Coinbene) WsConnect() error {
+ if !c.Websocket.IsEnabled() || !c.IsEnabled() {
+ return errors.New(wshandler.WebsocketNotEnabled)
+ }
+ var dialer websocket.Dialer
+ err := c.WebsocketConn.Dial(&dialer, http.Header{})
+ if err != nil {
+ return err
+ }
+ go c.WsDataHandler()
+ if c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
+ err = c.Login()
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ c.Websocket.SetCanUseAuthenticatedEndpoints(false)
+ }
+ }
+ c.GenerateDefaultSubscriptions()
+
+ return nil
+}
+
+// GenerateDefaultSubscriptions generates stuff
+func (c *Coinbene) GenerateDefaultSubscriptions() {
+ var channels = []string{"orderBook.%s.100", "tradeList.%s", "ticker.%s", "kline.%s"}
+ var subscriptions []wshandler.WebsocketChannelSubscription
+ for x := range channels {
+ for y := range c.EnabledPairs {
+ c.EnabledPairs[y].Delimiter = ""
+ subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
+ Channel: fmt.Sprintf(channels[x], c.EnabledPairs[y]),
+ Currency: c.EnabledPairs[y],
+ })
+ }
+ }
+ c.Websocket.SubscribeToChannels(subscriptions)
+}
+
+// GenerateAuthSubs generates auth subs
+func (c *Coinbene) GenerateAuthSubs() {
+ var subscriptions []wshandler.WebsocketChannelSubscription
+ var sub wshandler.WebsocketChannelSubscription
+ var userChannels = []string{"user.account", "user.position", "user.order"}
+ for z := range userChannels {
+ sub.Channel = userChannels[z]
+ subscriptions = append(subscriptions, sub)
+ }
+ c.Websocket.SubscribeToChannels(subscriptions)
+}
+
+// WsDataHandler handles websocket data
+func (c *Coinbene) WsDataHandler() {
+ c.Websocket.Wg.Add(1)
+
+ defer c.Websocket.Wg.Done()
+
+ for {
+ select {
+ case <-c.Websocket.ShutdownC:
+ return
+
+ default:
+ stream, err := c.WebsocketConn.ReadMessage()
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ return
+ }
+ c.Websocket.TrafficAlert <- struct{}{}
+ if string(stream.Raw) == "ping" {
+ c.WebsocketConn.Lock()
+ c.WebsocketConn.Connection.WriteMessage(websocket.TextMessage, []byte("pong"))
+ c.WebsocketConn.Unlock()
+ continue
+ }
+ var result map[string]interface{}
+ err = common.JSONDecode(stream.Raw, &result)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ }
+ _, ok := result[event]
+ switch {
+ case ok && (result[event].(string) == "subscribe" || result[event].(string) == "unsubscribe"):
+ continue
+ case ok && result[event].(string) == "error":
+ c.Websocket.DataHandler <- fmt.Errorf("message: %s. code: %v", result["message"], result["code"])
+ continue
+ }
+ if ok && strings.Contains(result[event].(string), "login") {
+ if result["success"].(bool) {
+ c.Websocket.SetCanUseAuthenticatedEndpoints(true)
+ c.GenerateAuthSubs()
+ continue
+ }
+ c.Websocket.SetCanUseAuthenticatedEndpoints(false)
+ c.Websocket.DataHandler <- fmt.Errorf("message: %s. code: %v", result["message"], result["code"])
+ continue
+ }
+ switch {
+ case strings.Contains(result[topic].(string), "ticker"):
+ var ticker WsTicker
+ err = common.JSONDecode(stream.Raw, &ticker)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ for x := range ticker.Data {
+ c.Websocket.DataHandler <- wshandler.TickerData{
+ Quantity: ticker.Data[x].Volume24h,
+ ClosePrice: ticker.Data[x].LastPrice,
+ HighPrice: ticker.Data[x].High24h,
+ LowPrice: ticker.Data[x].Low24h,
+ Pair: currency.NewPairFromString(ticker.Data[x].Symbol),
+ Exchange: c.Name,
+ AssetType: orderbook.Swap,
+ }
+ }
+ case strings.Contains(result[topic].(string), "tradeList"):
+ var tradeList WsTradeList
+ err = common.JSONDecode(stream.Raw, &tradeList)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ var t time.Time
+ var price, amount float64
+ t, err = time.Parse(time.RFC3339, tradeList.Data[0][3])
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ price, err = strconv.ParseFloat(tradeList.Data[0][0], 64)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ amount, err = strconv.ParseFloat(tradeList.Data[0][2], 64)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ c.Websocket.DataHandler <- wshandler.TradeData{
+ CurrencyPair: currency.NewPairFromString(strings.Replace(tradeList.Topic, "tradeList.", "", 1)),
+ Timestamp: t,
+ Price: price,
+ Amount: amount,
+ Exchange: c.Name,
+ AssetType: orderbook.Swap,
+ Side: tradeList.Data[0][1],
+ }
+ case strings.Contains(result[topic].(string), "orderBook"):
+ var orderBook WsOrderbook
+ err = common.JSONDecode(stream.Raw, &orderBook)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ 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 {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ price, err = strconv.ParseFloat(orderBook.Data[0].Asks[i][0], 64)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ asks = append(asks, orderbook.Item{
+ Amount: amount,
+ Price: price,
+ })
+ }
+ for j := range orderBook.Data[0].Bids {
+ amount, err = strconv.ParseFloat(orderBook.Data[0].Bids[j][1], 64)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ price, err = strconv.ParseFloat(orderBook.Data[0].Bids[j][0], 64)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ bids = append(bids, orderbook.Item{
+ Amount: amount,
+ Price: price,
+ })
+ }
+ if orderBook.Action == "insert" {
+ var newOB orderbook.Base
+ newOB.Asks = asks
+ newOB.Bids = bids
+ newOB.AssetType = orderbook.Swap
+ newOB.Pair = currency.NewPairFromString(strings.Replace(orderBook.Topic, "tradeList.", "", 1))
+ newOB.ExchangeName = c.Name
+ err = c.Websocket.Orderbook.LoadSnapshot(&newOB, true)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
+ Asset: orderbook.Swap,
+ Exchange: c.Name,
+ }
+ } else if orderBook.Action == "update" {
+ newOB := wsorderbook.WebsocketOrderbookUpdate{
+ Asks: asks,
+ Bids: bids,
+ AssetType: orderbook.Swap,
+ CurrencyPair: currency.NewPairFromString(strings.Replace(orderBook.Topic, "tradeList.", "", 1)),
+ UpdateID: orderBook.Version,
+ }
+ err = c.Websocket.Orderbook.Update(&newOB)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.CurrencyPair,
+ Asset: orderbook.Swap,
+ Exchange: c.Name,
+ }
+ }
+ case strings.Contains(result[topic].(string), "kline"):
+ var kline WsKline
+ var tempFloat float64
+ var tempKline []float64
+ err = common.JSONDecode(stream.Raw, &kline)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ for x := 2; x < len(kline.Data[0]); x++ {
+ tempFloat, err = strconv.ParseFloat(kline.Data[0][x].(string), 64)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ tempKline = append(tempKline, tempFloat)
+ }
+ c.Websocket.DataHandler <- wshandler.KlineData{
+ Timestamp: time.Unix(int64(kline.Data[0][1].(float64)), 0),
+ Pair: currency.NewPairFromString(kline.Data[0][0].(string)),
+ AssetType: orderbook.Swap,
+ Exchange: c.Name,
+ OpenPrice: tempKline[0],
+ ClosePrice: tempKline[1],
+ HighPrice: tempKline[2],
+ LowPrice: tempKline[3],
+ Volume: tempKline[4],
+ }
+ case strings.Contains(result[topic].(string), "user.account"):
+ var userinfo WsUserInfo
+ err = common.JSONDecode(stream.Raw, &userinfo)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ c.Websocket.DataHandler <- userinfo
+ case strings.Contains(result[topic].(string), "user.position"):
+ var position WsPosition
+ err = common.JSONDecode(stream.Raw, &position)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ c.Websocket.DataHandler <- position
+ case strings.Contains(result[topic].(string), "user.order"):
+ var orders WsUserOrders
+ err = common.JSONDecode(stream.Raw, &orders)
+ if err != nil {
+ c.Websocket.DataHandler <- err
+ continue
+ }
+ c.Websocket.DataHandler <- orders
+ default:
+ c.Websocket.DataHandler <- fmt.Errorf("%s - unhandled response '%s'", c.Name, stream.Raw)
+ }
+ }
+ }
+}
+
+// Subscribe sends a websocket message to receive data from the channel
+func (c *Coinbene) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
+ var sub WsSub
+ sub.Operation = "subscribe"
+ sub.Arguments = []string{channelToSubscribe.Channel}
+ return c.WebsocketConn.SendMessage(sub)
+}
+
+// Unsubscribe sends a websocket message to receive data from the channel
+func (c *Coinbene) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
+ var sub WsSub
+ sub.Operation = "unsubscribe"
+ sub.Arguments = []string{channelToSubscribe.Channel}
+ return c.WebsocketConn.SendMessage(sub)
+}
+
+// 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 := common.GetHMAC(common.HashSHA256, []byte(signMsg), []byte(c.APISecret))
+ sign := common.HexEncodeToString(tempSign)
+ sub.Operation = "login"
+ sub.Arguments = []string{c.APIKey, expTime, sign}
+ return c.WebsocketConn.SendMessage(sub)
+}
diff --git a/exchanges/coinbene/coinbene_wrapper.go b/exchanges/coinbene/coinbene_wrapper.go
new file mode 100644
index 00000000..90fd1c92
--- /dev/null
+++ b/exchanges/coinbene/coinbene_wrapper.go
@@ -0,0 +1,409 @@
+package coinbene
+
+import (
+ "fmt"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/thrasher-corp/gocryptotrader/common"
+ "github.com/thrasher-corp/gocryptotrader/currency"
+ exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
+ log "github.com/thrasher-corp/gocryptotrader/logger"
+)
+
+// Start starts the Coinbene go routine
+func (c *Coinbene) Start(wg *sync.WaitGroup) {
+ wg.Add(1)
+ go func() {
+ c.Run()
+ wg.Done()
+ }()
+}
+
+// Run implements the Coinbene wrapper
+func (c *Coinbene) Run() {
+ if c.Verbose {
+ log.Debugf("%s Websocket: %s. (url: %s).\n", c.Name, common.IsEnabled(c.Websocket.IsEnabled()), c.Websocket.GetWebsocketURL())
+ log.Debugf("%s polling delay: %ds.\n", c.Name, c.RESTPollingDelay)
+ log.Debugf("%s %d currencies enabled: %s.\n", c.Name, len(c.EnabledPairs), c.EnabledPairs)
+ }
+ exchangeCurrencies, err := c.GetAllPairs()
+ if err != nil {
+ log.Errorf("%s Failed to get available symbols.\n", c.Name)
+ } else {
+ var newExchangeCurrencies currency.Pairs
+ for p := range exchangeCurrencies.Data {
+ newExchangeCurrencies = append(newExchangeCurrencies,
+ currency.NewPairFromString(exchangeCurrencies.Data[p].Symbol))
+ }
+ err = c.UpdateCurrencies(newExchangeCurrencies, false, false)
+ if err != nil {
+ log.Errorf("%s Failed to update available currencies %s.\n",
+ c.Name,
+ err)
+ }
+ }
+}
+
+// UpdateTicker updates and returns the ticker for a currency pair
+func (c *Coinbene) UpdateTicker(p currency.Pair, assetType string) (ticker.Price, error) {
+ var resp ticker.Price
+ allPairs := c.GetEnabledCurrencies()
+ for x := range allPairs {
+ tempResp, err := c.FetchTicker(exchange.FormatExchangeCurrency(c.Name,
+ allPairs[x]).String())
+ if err != nil {
+ return resp, err
+ }
+ resp.Pair = allPairs[x]
+ resp.Last = tempResp.TickerData.LatestPrice
+ resp.High = tempResp.TickerData.DailyHigh
+ resp.Low = tempResp.TickerData.DailyLow
+ resp.Bid = tempResp.TickerData.BestBid
+ resp.Ask = tempResp.TickerData.BestAsk
+ resp.Volume = tempResp.TickerData.DailyVolume
+ resp.LastUpdated = time.Now()
+ err = ticker.ProcessTicker(c.Name, &resp, assetType)
+ if err != nil {
+ return resp, err
+ }
+ }
+ return ticker.GetTicker(c.Name, p, assetType)
+}
+
+// GetTickerPrice returns the ticker for a currency pair
+func (c *Coinbene) GetTickerPrice(p currency.Pair, assetType string) (ticker.Price, error) {
+ tickerNew, err := ticker.GetTicker(c.Name, p, assetType)
+ if err != nil {
+ return c.UpdateTicker(p, assetType)
+ }
+ return tickerNew, nil
+}
+
+// GetOrderbookEx returns orderbook base on the currency pair
+func (c *Coinbene) GetOrderbookEx(currency currency.Pair, assetType string) (orderbook.Base, error) {
+ ob, err := orderbook.Get(c.Name, currency, assetType)
+ if err != nil {
+ return c.UpdateOrderbook(currency, assetType)
+ }
+ return ob, nil
+}
+
+// UpdateOrderbook updates and returns the orderbook for a currency pair
+func (c *Coinbene) UpdateOrderbook(p currency.Pair, assetType string) (orderbook.Base, error) {
+ var resp orderbook.Base
+ strPair := exchange.FormatExchangeCurrency(c.Name, p).String()
+ tempResp, err := c.FetchOrderbooks(strPair, 100)
+ if err != nil {
+ return resp, err
+ }
+ resp.ExchangeName = c.Name
+ resp.Pair = p
+ resp.AssetType = assetType
+ var amount, price float64
+ for i := range tempResp.Orderbook.Asks {
+ amount, err = strconv.ParseFloat(tempResp.Orderbook.Asks[i][1], 64)
+ if err != nil {
+ return resp, err
+ }
+ price, err = strconv.ParseFloat(tempResp.Orderbook.Asks[i][0], 64)
+ if err != nil {
+ return resp, err
+ }
+ resp.Asks = append(resp.Asks, orderbook.Item{
+ Price: price,
+ Amount: amount})
+ }
+ for j := range tempResp.Orderbook.Bids {
+ amount, err = strconv.ParseFloat(tempResp.Orderbook.Bids[j][1], 64)
+ if err != nil {
+ return resp, err
+ }
+ price, err = strconv.ParseFloat(tempResp.Orderbook.Bids[j][0], 64)
+ if err != nil {
+ return resp, err
+ }
+ resp.Bids = append(resp.Bids, orderbook.Item{
+ Price: price,
+ Amount: amount})
+ }
+ err = resp.Process()
+ if err != nil {
+ return resp, err
+ }
+ return orderbook.Get(c.Name, p, assetType)
+}
+
+// GetAccountInfo retrieves balances for all enabled currencies for the
+// Coinbene exchange
+func (c *Coinbene) GetAccountInfo() (exchange.AccountInfo, error) {
+ var info exchange.AccountInfo
+ data, err := c.GetUserBalance()
+ if err != nil {
+ return info, err
+ }
+ var account exchange.Account
+ for key := range data.Data {
+ c := currency.NewCode(data.Data[key].Asset)
+ hold := data.Data[key].Reserved
+ available := data.Data[key].Available
+ account.Currencies = append(account.Currencies,
+ exchange.AccountCurrencyInfo{CurrencyName: c,
+ TotalValue: hold + available,
+ Hold: hold})
+ }
+ info.Accounts = append(info.Accounts, account)
+ info.Exchange = c.Name
+ return info, nil
+}
+
+// GetFundingHistory returns funding history, deposits and
+// withdrawals
+func (c *Coinbene) GetFundingHistory() ([]exchange.FundHistory, error) {
+ return nil, common.ErrFunctionNotSupported
+}
+
+// GetExchangeHistory returns historic trade data since exchange opening.
+func (c *Coinbene) GetExchangeHistory(p currency.Pair, assetType string) ([]exchange.TradeHistory, error) {
+ return nil, common.ErrFunctionNotSupported
+}
+
+// SubmitOrder submits a new order
+func (c *Coinbene) SubmitOrder(p currency.Pair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) {
+ var resp exchange.SubmitOrderResponse
+ if side != exchange.BuyOrderSide && side != exchange.SellOrderSide {
+ return resp,
+ fmt.Errorf("%s orderside is not supported by this exchange", side)
+ }
+ tempResp, err := c.PlaceOrder(price,
+ amount,
+ exchange.FormatExchangeCurrency(c.Name, p).String(),
+ orderType.ToString(),
+ clientID)
+ if err != nil {
+ return resp, err
+ }
+ resp.IsOrderPlaced = true
+ resp.OrderID = tempResp.OrderID
+ return resp, nil
+}
+
+// ModifyOrder will allow of changing orderbook placement and limit to
+// market conversion
+func (c *Coinbene) ModifyOrder(action *exchange.ModifyOrder) (string, error) {
+ return "", common.ErrFunctionNotSupported
+}
+
+// CancelOrder cancels an order by its corresponding ID number
+func (c *Coinbene) CancelOrder(order *exchange.OrderCancellation) error {
+ _, err := c.RemoveOrder(order.OrderID)
+ return err
+}
+
+// CancelAllOrders cancels all orders associated with a currency pair
+func (c *Coinbene) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) {
+ var resp exchange.CancelAllOrdersResponse
+ tempMap := make(map[string]string)
+ orders, err := c.FetchOpenOrders(exchange.FormatExchangeCurrency(c.Name,
+ orderCancellation.CurrencyPair).String())
+ if err != nil {
+ return resp, err
+ }
+ for x := range orders.OpenOrders {
+ _, err := c.RemoveOrder(orders.OpenOrders[x].OrderID)
+ if err != nil {
+ tempMap[orders.OpenOrders[x].OrderID] = "Failed"
+ } else {
+ tempMap[orders.OpenOrders[x].OrderID] = "Success"
+ }
+ }
+ resp.OrderStatus = tempMap
+ return resp, nil
+}
+
+// GetOrderInfo returns information on a current open order
+func (c *Coinbene) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
+ var resp exchange.OrderDetail
+ tempResp, err := c.FetchOrderInfo(orderID)
+ if err != nil {
+ return resp, err
+ }
+ var t time.Time
+ resp.Exchange = c.Name
+ resp.ID = orderID
+ resp.CurrencyPair = currency.NewPairWithDelimiter(tempResp.Order.BaseAsset,
+ "/",
+ tempResp.Order.QuoteAsset)
+ t, err = time.Parse(time.RFC3339, tempResp.Order.OrderTime)
+ if err != nil {
+ return resp, err
+ }
+ resp.Price = tempResp.Order.OrderPrice
+ resp.OrderDate = t
+ resp.ExecutedAmount = tempResp.Order.FilledAmount
+ resp.Fee = tempResp.Order.TotalFee
+ return resp, nil
+}
+
+// GetDepositAddress returns a deposit address for a specified currency
+func (c *Coinbene) GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) {
+ return "", common.ErrFunctionNotSupported
+}
+
+// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
+// submitted
+func (c *Coinbene) WithdrawCryptocurrencyFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) {
+ return "", common.ErrFunctionNotSupported
+}
+
+// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is
+// submitted
+func (c *Coinbene) WithdrawFiatFunds(withdrawRequest *exchange.WithdrawRequest) (string, error) {
+ return "", common.ErrFunctionNotSupported
+}
+
+// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is
+// submitted
+func (c *Coinbene) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.WithdrawRequest) (string, error) {
+ return "", common.ErrFunctionNotSupported
+}
+
+// GetWebsocket returns a pointer to the exchange websocket
+func (c *Coinbene) GetWebsocket() (*wshandler.Websocket, error) {
+ return c.Websocket, nil
+}
+
+// GetActiveOrders retrieves any orders that are active/open
+func (c *Coinbene) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
+ var resp []exchange.OrderDetail
+ var tempResp exchange.OrderDetail
+ var tempData OpenOrderResponse
+ if len(getOrdersRequest.Currencies) == 0 {
+ allPairs, err := c.GetAllPairs()
+ if err != nil {
+ return resp, err
+ }
+ for a := range allPairs.Data {
+ getOrdersRequest.Currencies = append(getOrdersRequest.Currencies, currency.NewPairFromString(allPairs.Data[a].Symbol))
+ }
+ }
+ var err error
+ for x := range getOrdersRequest.Currencies {
+ tempData, err = c.FetchOpenOrders(exchange.FormatExchangeCurrency(c.Name, getOrdersRequest.Currencies[x]).String())
+ if err != nil {
+ return resp, err
+ }
+ var t time.Time
+ for y := range tempData.OpenOrders {
+ tempResp.Exchange = c.Name
+ tempResp.CurrencyPair = getOrdersRequest.Currencies[x]
+ tempResp.OrderSide = buy
+ if tempData.OpenOrders[y].OrderType == sell {
+ tempResp.OrderSide = sell
+ }
+ t, err = time.Parse(time.RFC3339, tempData.OpenOrders[y].OrderTime)
+ if err != nil {
+ return resp, err
+ }
+ tempResp.OrderDate = t
+ tempResp.Status = tempData.OpenOrders[y].OrderStatus
+ tempResp.Price = tempData.OpenOrders[y].OrderPrice
+ tempResp.Amount = tempData.OpenOrders[y].Amount
+ tempResp.ExecutedAmount = tempData.OpenOrders[y].FilledAmount
+ tempResp.RemainingAmount = tempData.OpenOrders[y].Amount - tempData.OpenOrders[y].FilledAmount
+ tempResp.Fee = tempData.OpenOrders[y].TotalFee
+ resp = append(resp, tempResp)
+ }
+ }
+ return resp, nil
+}
+
+// GetOrderHistory retrieves account order information
+// Can Limit response to specific order status
+func (c *Coinbene) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
+ var resp []exchange.OrderDetail
+ var tempResp exchange.OrderDetail
+ var tempData ClosedOrderResponse
+ if len(getOrdersRequest.Currencies) == 0 {
+ allPairs, err := c.GetAllPairs()
+ if err != nil {
+ return resp, err
+ }
+ for a := range allPairs.Data {
+ getOrdersRequest.Currencies = append(getOrdersRequest.Currencies, currency.NewPairFromString(allPairs.Data[a].Symbol))
+ }
+ }
+ var err error
+ for x := range getOrdersRequest.Currencies {
+ tempData, err = c.FetchClosedOrders(exchange.FormatExchangeCurrency(c.Name, getOrdersRequest.Currencies[x]).String(), "")
+ if err != nil {
+ return resp, err
+ }
+ var t time.Time
+ for y := range tempData.Data {
+ tempResp.Exchange = c.Name
+ tempResp.CurrencyPair = getOrdersRequest.Currencies[x]
+ tempResp.OrderSide = exchange.BuyOrderSide
+ if tempData.Data[y].OrderType == sell {
+ tempResp.OrderSide = exchange.SellOrderSide
+ }
+ t, err = time.Parse(time.RFC3339, tempData.Data[y].OrderTime)
+ if err != nil {
+ return resp, err
+ }
+ tempResp.OrderDate = t
+ tempResp.Status = tempData.Data[y].OrderStatus
+ tempResp.Price = tempData.Data[y].OrderPrice
+ tempResp.Amount = tempData.Data[y].Amount
+ tempResp.ExecutedAmount = tempData.Data[y].FilledAmount
+ tempResp.RemainingAmount = tempData.Data[y].Amount - tempData.Data[y].FilledAmount
+ tempResp.Fee = tempData.Data[y].TotalFee
+ resp = append(resp, tempResp)
+ }
+ }
+ return resp, nil
+}
+
+// GetFeeByType returns an estimate of fee based on the type of transaction
+func (c *Coinbene) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
+ var fee float64
+ tempData, err := c.GetPairInfo(exchange.FormatExchangeCurrency(c.Name, feeBuilder.Pair).String())
+ if err != nil {
+ return fee, err
+ }
+ switch feeBuilder.IsMaker {
+ case true:
+ fee = feeBuilder.PurchasePrice * feeBuilder.Amount * tempData.Data.MakerFeeRate
+ case false:
+ fee = feeBuilder.PurchasePrice * feeBuilder.Amount * tempData.Data.TakerFeeRate
+ }
+ return fee, nil
+}
+
+// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
+// which lets websocket.manageSubscriptions handle subscribing
+func (c *Coinbene) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
+ c.Websocket.SubscribeToChannels(channels)
+ return nil
+}
+
+// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
+// which lets websocket.manageSubscriptions handle unsubscribing
+func (c *Coinbene) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
+ c.Websocket.RemoveSubscribedChannels(channels)
+ return nil
+}
+
+// GetSubscriptions returns a copied list of subscriptions
+func (c *Coinbene) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
+ return c.Websocket.GetSubscriptions(), nil
+}
+
+// AuthenticateWebsocket sends an authentication message to the websocket
+func (c *Coinbene) AuthenticateWebsocket() error {
+ return c.Login()
+}
diff --git a/exchanges/exchange.go b/exchanges/exchange.go
index 19e34fd3..e6225344 100644
--- a/exchanges/exchange.go
+++ b/exchanges/exchange.go
@@ -881,6 +881,9 @@ func (e *Base) SetAPIURL(ec *config.ExchangeConfig) error {
if ec.APIURL != config.APIURLNonDefaultMessage {
e.APIUrl = ec.APIURL
}
+ if !strings.Contains(e.APIUrl, "https") {
+ log.Warnf("%s is using HTTP instead of HTTPS for API functionality, an attacker could eavesdrop on this connection. Use at your own risk", e.Name)
+ }
if ec.APIURLSecondary != config.APIURLNonDefaultMessage {
e.APIUrlSecondary = ec.APIURLSecondary
}
diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go
index d8393e2f..572c98e5 100644
--- a/exchanges/orderbook/orderbook.go
+++ b/exchanges/orderbook/orderbook.go
@@ -17,6 +17,7 @@ const (
errQuoteCurrencyNotFound = "orderbook quote currency not found"
Spot = "SPOT"
+ Swap = "SWAP"
)
// Vars for the orderbook package
diff --git a/testdata/configtest.json b/testdata/configtest.json
index 0677a111..6e33885b 100644
--- a/testdata/configtest.json
+++ b/testdata/configtest.json
@@ -1400,6 +1400,52 @@
"supportedCurrencies": ""
}
]
+ },
+ {
+ "name": "Coinbene",
+ "enabled": true,
+ "verbose": false,
+ "websocket": false,
+ "useSandbox": false,
+ "restPollingDelay": 10,
+ "httpTimeout": 0,
+ "websocketResponseCheckTimeout": 0,
+ "websocketResponseMaxLimit": 0,
+ "websocketOrderbookBufferLimit": 0,
+ "httpUserAgent": "",
+ "httpDebugging": false,
+ "authenticatedApiSupport": false,
+ "authenticatedWebsocketApiSupport": false,
+ "apiKey": "Key",
+ "apiSecret": "Secret",
+ "apiUrl": "",
+ "apiUrlSecondary": "",
+ "proxyAddress": "",
+ "websocketUrl": "",
+ "availablePairs": "BTC/USDT",
+ "enabledPairs": "BTC/USDT",
+ "baseCurrencies": "USD",
+ "assetTypes": "SPOT",
+ "supportsAutoPairUpdates": true,
+ "configCurrencyPairFormat": {
+ "uppercase": true,
+ "delimiter": "/"
+ },
+ "requestCurrencyPairFormat": {
+ "uppercase": true,
+ "delimiter": "/"
+ },
+ "bankAccounts": [
+ {
+ "bankName": "",
+ "bankAddress": "",
+ "accountName": "",
+ "accountNumber": "",
+ "swiftCode": "",
+ "iban": "",
+ "supportedCurrencies": ""
+ }
+ ]
}
],
"bankAccounts": [
diff --git a/tools/documentation/documentation.go b/tools/documentation/documentation.go
index db794179..707460d3 100644
--- a/tools/documentation/documentation.go
+++ b/tools/documentation/documentation.go
@@ -54,6 +54,7 @@ const (
bittrex = "..%s..%sexchanges%sbittrex%s"
btcmarkets = "..%s..%sexchanges%sbtcmarkets%s"
coinbasepro = "..%s..%sexchanges%scoinbasepro%s"
+ coinbene = "..%s..%sexchanges%scoinbene%s"
coinut = "..%s..%sexchanges%scoinut%s"
exmo = "..%s..%sexchanges%sexmo%s"
gateio = "..%s..%sexchanges%sgateio%s"
@@ -225,6 +226,7 @@ func addPaths() {
codebasePaths["exchanges coinut"] = fmt.Sprintf(coinut, path, path, path, path)
codebasePaths["exchanges exmo"] = fmt.Sprintf(exmo, path, path, path, path)
codebasePaths["exchanges coinbasepro"] = fmt.Sprintf(coinbasepro, path, path, path, path)
+ codebasePaths["exchanges coinbene"] = fmt.Sprintf(coinbene, path, path, path, path)
codebasePaths["exchanges gateio"] = fmt.Sprintf(gateio, path, path, path, path)
codebasePaths["exchanges gemini"] = fmt.Sprintf(gemini, path, path, path, path)
codebasePaths["exchanges hitbtc"] = fmt.Sprintf(hitbtc, path, path, path, path)
diff --git a/tools/documentation/exchanges_templates/coinbene.tmpl b/tools/documentation/exchanges_templates/coinbene.tmpl
new file mode 100644
index 00000000..f9de7d4c
--- /dev/null
+++ b/tools/documentation/exchanges_templates/coinbene.tmpl
@@ -0,0 +1,106 @@
+{{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.GetTickerPrice()
+if err != nil {
+ // Handle error
+}
+
+// Fetches current orderbook information
+ob, err := c.GetOrderbookEx()
+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
+tradeID, err := c.Trade(...)
+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}}