mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* drop common uuid v4 func and imported package as needed * removed common functions regarding json marshal and unmarshal and used the json package directly. WRT unmarshal it was calling reflect and converted to string which is also checked in the JSON package so it was doing a double up, this will be a tiny gain as it was directly used in the requester package for all our outbound requests. * add in string * explicitly throw away return error value * atleast return the error that websocket initialise returns * return error when not connected * fix comment * Adds comments * move package declarations * drop append whenever we call supported * remove unused import * Change incorrect spelling * fix tests * fix go import issue
428 lines
12 KiB
Go
428 lines
12 KiB
Go
package coinut
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"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/websocket/wshandler"
|
|
log "github.com/thrasher-corp/gocryptotrader/logger"
|
|
)
|
|
|
|
const (
|
|
coinutAPIURL = "https://api.coinut.com"
|
|
coinutAPIVersion = "1"
|
|
coinutInstruments = "inst_list"
|
|
coinutTicker = "inst_tick"
|
|
coinutOrderbook = "inst_order_book"
|
|
coinutTrades = "inst_trade"
|
|
coinutBalance = "user_balance"
|
|
coinutOrder = "new_order"
|
|
coinutOrders = "new_orders"
|
|
coinutOrdersOpen = "user_open_orders"
|
|
coinutOrderCancel = "cancel_order"
|
|
coinutOrdersCancel = "cancel_orders"
|
|
coinutTradeHistory = "trade_history"
|
|
coinutIndexTicker = "index_tick"
|
|
coinutOptionChain = "option_chain"
|
|
coinutPositionHistory = "position_history"
|
|
coinutPositionOpen = "user_open_positions"
|
|
|
|
coinutAuthRate = 0
|
|
coinutUnauthRate = 0
|
|
|
|
coinutStatusOK = "OK"
|
|
)
|
|
|
|
var (
|
|
errLookupInstrumentID = errors.New("unable to lookup instrument ID")
|
|
errLookupInstrumentCurrency = errors.New("unable to lookup instrument")
|
|
)
|
|
|
|
// COINUT is the overarching type across the coinut package
|
|
type COINUT struct {
|
|
exchange.Base
|
|
WebsocketConn *wshandler.WebsocketConnection
|
|
instrumentMap instrumentMap
|
|
}
|
|
|
|
// SeedInstruments seeds the instrument map
|
|
func (c *COINUT) SeedInstruments() error {
|
|
i, err := c.GetInstruments()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, y := range i.Instruments {
|
|
c.instrumentMap.Seed(y[0].Base+y[0].Quote, y[0].InstID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetInstruments returns instruments
|
|
func (c *COINUT) GetInstruments() (Instruments, error) {
|
|
var result Instruments
|
|
params := make(map[string]interface{})
|
|
params["sec_type"] = strings.ToUpper(asset.Spot.String())
|
|
return result, c.SendHTTPRequest(coinutInstruments, params, false, &result)
|
|
}
|
|
|
|
// GetInstrumentTicker returns a ticker for a specific instrument
|
|
func (c *COINUT) GetInstrumentTicker(instrumentID int64) (Ticker, error) {
|
|
var result Ticker
|
|
params := make(map[string]interface{})
|
|
params["inst_id"] = instrumentID
|
|
return result, c.SendHTTPRequest(coinutTicker, params, false, &result)
|
|
}
|
|
|
|
// GetInstrumentOrderbook returns the orderbooks for a specific instrument
|
|
func (c *COINUT) GetInstrumentOrderbook(instrumentID, limit int64) (Orderbook, error) {
|
|
var result Orderbook
|
|
params := make(map[string]interface{})
|
|
params["inst_id"] = instrumentID
|
|
if limit > 0 {
|
|
params["top_n"] = limit
|
|
}
|
|
|
|
return result, c.SendHTTPRequest(coinutOrderbook, params, false, &result)
|
|
}
|
|
|
|
// GetTrades returns trade information
|
|
func (c *COINUT) GetTrades(instrumentID int) (Trades, error) {
|
|
var result Trades
|
|
params := make(map[string]interface{})
|
|
params["inst_id"] = instrumentID
|
|
|
|
return result, c.SendHTTPRequest(coinutTrades, params, false, &result)
|
|
}
|
|
|
|
// GetUserBalance returns the full user balance
|
|
func (c *COINUT) GetUserBalance() (UserBalance, error) {
|
|
result := UserBalance{}
|
|
|
|
return result, c.SendHTTPRequest(coinutBalance, nil, true, &result)
|
|
}
|
|
|
|
// NewOrder places a new order on the exchange
|
|
func (c *COINUT) NewOrder(instrumentID int64, quantity, price float64, buy bool, orderID uint32) (interface{}, error) {
|
|
var result interface{}
|
|
params := make(map[string]interface{})
|
|
params["inst_id"] = instrumentID
|
|
if price > 0 {
|
|
params["price"] = fmt.Sprintf("%v", price)
|
|
}
|
|
params["qty"] = fmt.Sprintf("%v", quantity)
|
|
params["side"] = order.Buy.String()
|
|
if !buy {
|
|
params["side"] = order.Sell.String()
|
|
}
|
|
params["client_ord_id"] = orderID
|
|
|
|
err := c.SendHTTPRequest(coinutOrder, params, true, &result)
|
|
if _, ok := result.(OrderRejectResponse); ok {
|
|
return result.(OrderRejectResponse), err
|
|
}
|
|
if _, ok := result.(OrderFilledResponse); ok {
|
|
return result.(OrderFilledResponse), err
|
|
}
|
|
if _, ok := result.(OrdersBase); ok {
|
|
return result.(OrdersBase), err
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
// NewOrders places multiple orders on the exchange
|
|
func (c *COINUT) NewOrders(orders []Order) ([]OrdersBase, error) {
|
|
var result OrdersResponse
|
|
params := make(map[string]interface{})
|
|
params["orders"] = orders
|
|
|
|
return result.Data, c.SendHTTPRequest(coinutOrders, params, true, &result.Data)
|
|
}
|
|
|
|
// GetOpenOrders returns a list of open order and relevant information
|
|
func (c *COINUT) GetOpenOrders(instrumentID int64) (GetOpenOrdersResponse, error) {
|
|
var result GetOpenOrdersResponse
|
|
params := make(map[string]interface{})
|
|
params["inst_id"] = instrumentID
|
|
return result, c.SendHTTPRequest(coinutOrdersOpen, params, true, &result)
|
|
}
|
|
|
|
// CancelExistingOrder cancels a specific order and returns if it was actioned
|
|
func (c *COINUT) CancelExistingOrder(instrumentID, orderID int64) (bool, error) {
|
|
var result GenericResponse
|
|
params := make(map[string]interface{})
|
|
type Request struct {
|
|
InstrumentID int64 `json:"inst_id"`
|
|
OrderID int64 `json:"order_id"`
|
|
}
|
|
|
|
var entry = Request{
|
|
InstrumentID: instrumentID,
|
|
OrderID: orderID,
|
|
}
|
|
|
|
entries := []Request{entry}
|
|
params["entries"] = entries
|
|
|
|
err := c.SendHTTPRequest(coinutOrdersCancel, params, true, &result)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// CancelOrders cancels multiple orders
|
|
func (c *COINUT) CancelOrders(orders []CancelOrders) (CancelOrdersResponse, error) {
|
|
var result CancelOrdersResponse
|
|
params := make(map[string]interface{})
|
|
type Request struct {
|
|
InstrumentID int `json:"inst_id"`
|
|
OrderID int `json:"order_id"`
|
|
}
|
|
|
|
var entries []CancelOrders
|
|
entries = append(entries, orders...)
|
|
params["entries"] = entries
|
|
|
|
return result, c.SendHTTPRequest(coinutOrdersCancel, params, true, &result)
|
|
}
|
|
|
|
// GetTradeHistory returns trade history for a specific instrument.
|
|
func (c *COINUT) GetTradeHistory(instrumentID, start, limit int64) (TradeHistory, error) {
|
|
var result TradeHistory
|
|
params := make(map[string]interface{})
|
|
params["inst_id"] = instrumentID
|
|
if start >= 0 && start <= 100 {
|
|
params["start"] = start
|
|
}
|
|
if limit >= 0 && start <= 100 {
|
|
params["limit"] = limit
|
|
}
|
|
|
|
return result, c.SendHTTPRequest(coinutTradeHistory, params, true, &result)
|
|
}
|
|
|
|
// GetIndexTicker returns the index ticker for an asset
|
|
func (c *COINUT) GetIndexTicker(asset string) (IndexTicker, error) {
|
|
var result IndexTicker
|
|
params := make(map[string]interface{})
|
|
params["asset"] = asset
|
|
|
|
return result, c.SendHTTPRequest(coinutIndexTicker, params, false, &result)
|
|
}
|
|
|
|
// GetDerivativeInstruments returns a list of derivative instruments
|
|
func (c *COINUT) GetDerivativeInstruments(secType string) (interface{}, error) {
|
|
var result interface{} // to-do
|
|
params := make(map[string]interface{})
|
|
params["sec_type"] = secType
|
|
|
|
return result, c.SendHTTPRequest(coinutInstruments, params, false, &result)
|
|
}
|
|
|
|
// GetOptionChain returns option chain
|
|
func (c *COINUT) GetOptionChain(asset, secType string) (OptionChainResponse, error) {
|
|
var result OptionChainResponse
|
|
params := make(map[string]interface{})
|
|
params["asset"] = asset
|
|
params["sec_type"] = secType
|
|
|
|
return result, c.SendHTTPRequest(coinutOptionChain, params, false, &result)
|
|
}
|
|
|
|
// GetPositionHistory returns position history
|
|
func (c *COINUT) GetPositionHistory(secType string, start, limit int) (PositionHistory, error) {
|
|
var result PositionHistory
|
|
params := make(map[string]interface{})
|
|
params["sec_type"] = secType
|
|
if start >= 0 {
|
|
params["start"] = start
|
|
}
|
|
if limit >= 0 {
|
|
params["limit"] = limit
|
|
}
|
|
|
|
return result, c.SendHTTPRequest(coinutPositionHistory, params, true, &result)
|
|
}
|
|
|
|
// GetOpenPositions returns all your current opened positions
|
|
func (c *COINUT) GetOpenPositions(instrumentID int) ([]OpenPosition, error) {
|
|
type Response struct {
|
|
Positions []OpenPosition `json:"positions"`
|
|
}
|
|
var result Response
|
|
params := make(map[string]interface{})
|
|
params["inst_id"] = instrumentID
|
|
|
|
return result.Positions,
|
|
c.SendHTTPRequest(coinutPositionOpen, params, true, &result)
|
|
}
|
|
|
|
// to-do: user position update via websocket
|
|
|
|
// SendHTTPRequest sends either an authenticated or unauthenticated HTTP request
|
|
func (c *COINUT) SendHTTPRequest(apiRequest string, params map[string]interface{}, authenticated bool, result interface{}) (err error) {
|
|
if !c.API.AuthenticatedSupport && authenticated {
|
|
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, c.Name)
|
|
}
|
|
|
|
n := c.Requester.GetNonce(false)
|
|
|
|
if params == nil {
|
|
params = map[string]interface{}{}
|
|
}
|
|
params["nonce"] = n
|
|
params["request"] = apiRequest
|
|
|
|
payload, err := json.Marshal(params)
|
|
if err != nil {
|
|
return errors.New("sendHTTPRequest: Unable to JSON request")
|
|
}
|
|
|
|
if c.Verbose {
|
|
log.Debugf(log.ExchangeSys, "Request JSON: %s", payload)
|
|
}
|
|
|
|
headers := make(map[string]string)
|
|
if authenticated {
|
|
headers["X-USER"] = c.API.Credentials.ClientID
|
|
hmac := crypto.GetHMAC(crypto.HashSHA256, payload, []byte(c.API.Credentials.Key))
|
|
headers["X-SIGNATURE"] = crypto.HexEncodeToString(hmac)
|
|
}
|
|
headers["Content-Type"] = "application/json"
|
|
|
|
var rawMsg json.RawMessage
|
|
err = c.SendPayload(http.MethodPost,
|
|
c.API.Endpoints.URL,
|
|
headers,
|
|
bytes.NewBuffer(payload),
|
|
&rawMsg,
|
|
authenticated,
|
|
true,
|
|
c.Verbose,
|
|
c.HTTPDebugging,
|
|
c.HTTPRecording)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var genResp GenericResponse
|
|
err = json.Unmarshal(rawMsg, &genResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if genResp.Status[0] != coinutStatusOK {
|
|
return fmt.Errorf("%s SendHTTPRequest error: %s", c.Name,
|
|
genResp.Status[0])
|
|
}
|
|
|
|
return json.Unmarshal(rawMsg, result)
|
|
}
|
|
|
|
// GetFee returns an estimate of fee based on type of transaction
|
|
func (c *COINUT) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
|
var fee float64
|
|
switch feeBuilder.FeeType {
|
|
case exchange.CryptocurrencyTradeFee:
|
|
fee = c.calculateTradingFee(feeBuilder.Pair.Base,
|
|
feeBuilder.Pair.Quote,
|
|
feeBuilder.PurchasePrice,
|
|
feeBuilder.Amount,
|
|
feeBuilder.IsMaker)
|
|
case exchange.InternationalBankWithdrawalFee:
|
|
fee = getInternationalBankWithdrawalFee(feeBuilder.FiatCurrency,
|
|
feeBuilder.Amount)
|
|
case exchange.InternationalBankDepositFee:
|
|
fee = getInternationalBankDepositFee(feeBuilder.FiatCurrency,
|
|
feeBuilder.Amount)
|
|
case exchange.OfflineTradeFee:
|
|
fee = getOfflineTradeFee(feeBuilder.Pair, feeBuilder.PurchasePrice, feeBuilder.Amount)
|
|
}
|
|
|
|
if fee < 0 {
|
|
fee = 0
|
|
}
|
|
|
|
return fee, nil
|
|
}
|
|
|
|
// getOfflineTradeFee calculates the worst case-scenario trading fee
|
|
func getOfflineTradeFee(c currency.Pair, price, amount float64) float64 {
|
|
if c.IsCryptoFiatPair() {
|
|
return 0.0035 * price * amount
|
|
}
|
|
return 0.002 * price * amount
|
|
}
|
|
|
|
func (c *COINUT) calculateTradingFee(base, quote currency.Code, purchasePrice, amount float64, isMaker bool) float64 {
|
|
var fee float64
|
|
|
|
switch {
|
|
case isMaker:
|
|
fee = 0
|
|
case currency.NewPair(base, quote).IsCryptoFiatPair():
|
|
fee = 0.002
|
|
default:
|
|
fee = 0.001
|
|
}
|
|
|
|
return fee * amount * purchasePrice
|
|
}
|
|
|
|
func getInternationalBankWithdrawalFee(c currency.Code, amount float64) float64 {
|
|
var fee float64
|
|
|
|
switch c {
|
|
case currency.USD:
|
|
if amount*0.001 < 10 {
|
|
fee = 10
|
|
} else {
|
|
fee = amount * 0.001
|
|
}
|
|
case currency.CAD:
|
|
if amount*0.005 < 10 {
|
|
fee = 2
|
|
} else {
|
|
fee = amount * 0.005
|
|
}
|
|
case currency.SGD:
|
|
if amount*0.001 < 10 {
|
|
fee = 10
|
|
} else {
|
|
fee = amount * 0.001
|
|
}
|
|
}
|
|
|
|
return fee
|
|
}
|
|
|
|
func getInternationalBankDepositFee(c currency.Code, amount float64) float64 {
|
|
var fee float64
|
|
|
|
if c == currency.USD {
|
|
if amount*0.001 < 10 {
|
|
fee = 10
|
|
} else {
|
|
fee = amount * 0.001
|
|
}
|
|
} else if c == currency.CAD {
|
|
if amount*0.005 < 10 {
|
|
fee = 2
|
|
} else {
|
|
fee = amount * 0.005
|
|
}
|
|
}
|
|
|
|
return fee
|
|
}
|