Files
gocryptotrader/exchanges/coinbene/coinbene.go
Adrian Gallagher 63191ce3ec Engine QA (#381)
* 1) Update Dockerfile/docker-compose.yml
2) Remove inline strings for buy/sell/test pairs
3) Remove dangerous order submission values
4) Fix consistency with audit_events (all other spec files use
CamelCase)
5) Update web websocket endpoint
6) Fix main param set (and induce dryrun mode on specific command line
params)

* Engine QA

Link up exchange syncer to cmd params, disarm market selling bombs and fix OKEX endpoints

* Fix linter issue after merge

* Engine QA changes

Template updates
Wrapper code cleanup
Disarmed order bombs
Documentation updates

* Daily engine QA

Bitstamp improvements
Spelling mistakes
Add Coinbene exchange to support list
Protect API authenticated calls for Coinbene/LBank

* Engine QA changes

Fix exchange_wrapper_coverage tool
Add SupportsAsset to exchange interface
Fix inline string usage and add BCH withdrawal support

* Engine QA

Fix Bitstamp types
Inform user of errors when parsing time accross the codebase
Change time parsing warnings to errors (as they are)
Update markdown docs [with linter fixes]

* Engine QA changes

1) Add test for dryrunParamInteraction
2) Disarm OKCoin/OKEX bombs if someone accidently sets canManipulateRealOrders to true and runs all package tests
3) Actually check exchange setup errors for BTSE and Coinbene, plus address this in the wrapper template
4) Hardcode missing/non-retrievable contributors and bump the contributors
5) Convert numbers/strings to meaningful types in Bitstamp and OKEX
6) If WS is supported for the exchange wrapper template, preset authWebsocketSupport var

* Fix the shadow people

* Link the SyncContinuously paramerino

* Also show SyncContinuously in engine.PrintSettings

* Address nitterinos and use correct filepath for logs

* Bitstamp: Extract ALL THE APM

* Fix additional nitterinos

* Fix time parsing error for Bittrex
2019-11-22 16:07:30 +11:00

298 lines
8.6 KiB
Go

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/common/crypto"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
// 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
)
// GetTicker gets and stores ticker data for a currency pair
func (c *Coinbene) GetTicker(symbol string) (TickerResponse, error) {
var t TickerResponse
params := url.Values{}
params.Set("symbol", symbol)
path := common.EncodeURLValues(c.API.Endpoints.URL+coinbeneAPIVersion+coinbeneFetchTicker, params)
return t, c.SendHTTPRequest(path, &t)
}
// GetOrderbook gets and stores orderbook data for given pair
func (c *Coinbene) GetOrderbook(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.API.Endpoints.URL+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.API.Endpoints.URL+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.API.Endpoints.URL+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.API.Endpoints.URL + coinbeneAPIVersion + coinbeneGetAllPairs
return a, c.SendHTTPRequest(path, &a)
}
// GetUserBalance gets user balanace info
func (c *Coinbene) GetUserBalance() (UserBalanceResponse, error) {
var resp UserBalanceResponse
path := c.API.Endpoints.URL + 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.API.Endpoints.URL + 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.API.Endpoints.URL + 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.API.Endpoints.URL + 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.API.Endpoints.URL + 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.API.Endpoints.URL + 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 !c.AllowAuthenticatedRequest() {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, c.Name)
}
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 := crypto.GetHMAC(crypto.HashSHA256,
[]byte(preSign),
[]byte(c.API.Credentials.Secret))
headers := make(map[string]string)
headers["Content-Type"] = "application/json"
headers["ACCESS-KEY"] = c.API.Credentials.Key
headers["ACCESS-SIGN"] = crypto.HexEncodeToString(tempSign)
headers["ACCESS-TIMESTAMP"] = timestamp
return c.SendPayload(method,
path,
headers,
finalBody,
&result,
true,
false,
c.Verbose,
c.HTTPDebugging,
c.HTTPRecording)
}