Add basic support for COINUT exchange

This commit is contained in:
Adrian Gallagher
2017-06-27 17:03:04 +10:00
parent 3c53fe9419
commit 093def35e9
8 changed files with 748 additions and 5 deletions

324
exchanges/coinut/coinut.go Normal file
View File

@@ -0,0 +1,324 @@
package coinut
import (
"bytes"
"errors"
"log"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
)
const (
COINUT_API_URL = "https://api.coinut.com"
COINUT_API_VERISON = "1"
COINUT_INSTRUMENTS = "inst_list"
COINUT_TICKER = "inst_tick"
COINUT_ORDERBOOK = "inst_order_book"
COINUT_TRADES = "inst_trade"
COINUT_BALANCE = "user_balance"
COINUT_ORDER = "new_order"
COINUT_ORDERS = "new_orders"
COINUT_ORDERS_OPEN = "user_open_orders"
COINUT_ORDER_CANCEL = "cancel_order"
COINUT_ORDERS_CANCEL = "cancel_orders"
COINUT_TRADE_HISTORY = "trade_history"
COINUT_INDEX_TICKER = "index_tick"
COINUT_OPTION_CHAIN = "option_chain"
COINUT_POSITION_HISTORY = "position_history"
COINUT_POSITION_OPEN = "user_open_positions"
)
type COINUT struct {
exchange.ExchangeBase
WebsocketConn *websocket.Conn
InstrumentMap map[string]int
}
func (c *COINUT) SetDefaults() {
c.Name = "COINUT"
c.Enabled = false
c.Verbose = false
c.TakerFee = 0.1 //spot
c.MakerFee = 0
c.Verbose = false
c.Websocket = false
c.RESTPollingDelay = 10
}
func (c *COINUT) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
c.SetEnabled(false)
} else {
c.Enabled = true
c.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
c.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, true)
c.RESTPollingDelay = exch.RESTPollingDelay
c.Verbose = exch.Verbose
c.Websocket = exch.Websocket
c.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
c.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
c.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
}
}
func (c *COINUT) GetInstruments() (CoinutInstruments, error) {
var result CoinutInstruments
params := make(map[string]interface{})
params["sec_type"] = "SPOT"
err := c.SendAuthenticatedHTTPRequest(COINUT_INSTRUMENTS, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetInstrumentTicker(instrumentID int) (CoinutTicker, error) {
var result CoinutTicker
params := make(map[string]interface{})
params["inst_id"] = instrumentID
err := c.SendAuthenticatedHTTPRequest(COINUT_TICKER, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetInstrumentOrderbook(instrumentID, limit int) (CoinutOrderbook, error) {
var result CoinutOrderbook
params := make(map[string]interface{})
params["inst_id"] = instrumentID
if limit > 0 {
params["top_n"] = limit
}
err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERBOOK, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetTrades(instrumentID int) (CoinutTrades, error) {
var result CoinutTrades
params := make(map[string]interface{})
params["inst_id"] = instrumentID
err := c.SendAuthenticatedHTTPRequest(COINUT_TRADES, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetUserBalance() (CoinutUserBalance, error) {
result := CoinutUserBalance{}
err := c.SendAuthenticatedHTTPRequest(COINUT_BALANCE, nil, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) NewOrder(instrumentID int, quantity, price float64, buy bool, orderID uint32) (interface{}, error) {
var result interface{}
params := make(map[string]interface{})
params["inst_id"] = instrumentID
params["price"] = price
params["qty"] = quantity
params["side"] = "BUY"
if !buy {
params["side"] = "SELL"
}
params["client_ord_id"] = orderID
err := c.SendAuthenticatedHTTPRequest(COINUT_ORDER, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) NewOrders(orders []CoinutOrder) ([]CoinutOrdersBase, error) {
var result CoinutOrdersResponse
params := make(map[string]interface{})
params["orders"] = orders
err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS, params, &result.Data)
if err != nil {
return nil, err
}
return result.Data, nil
}
func (c *COINUT) GetOpenOrders(instrumentID int) ([]CoinutOrdersResponse, error) {
var result []CoinutOrdersResponse
params := make(map[string]interface{})
params["inst_id"] = instrumentID
err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_OPEN, params, &result)
if err != nil {
return nil, err
}
return result, nil
}
func (c *COINUT) CancelOrder(instrumentID, orderID int) (bool, error) {
var result CoinutGenericResponse
params := make(map[string]interface{})
params["inst_id"] = instrumentID
params["order_id"] = orderID
err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_CANCEL, params, &result)
if err != nil {
return false, err
}
return true, nil
}
func (c *COINUT) CancelOrders(orders []CoinutCancelOrders) (CoinutCancelOrdersResponse, error) {
var result CoinutCancelOrdersResponse
params := make(map[string]interface{})
params["entries"] = orders
err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_CANCEL, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetTradeHistory(instrumentID, start, limit int) (CoinutTradeHistory, error) {
var result CoinutTradeHistory
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
}
err := c.SendAuthenticatedHTTPRequest(COINUT_TRADE_HISTORY, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetIndexTicker(asset string) (CoinutIndexTicker, error) {
var result CoinutIndexTicker
params := make(map[string]interface{})
params["asset"] = asset
err := c.SendAuthenticatedHTTPRequest(COINUT_INDEX_TICKER, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetDerivativeInstruments(secType string) (interface{}, error) {
var result interface{} //to-do
params := make(map[string]interface{})
params["sec_type"] = secType
err := c.SendAuthenticatedHTTPRequest(COINUT_INSTRUMENTS, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetOptionChain(asset, secType string, expiry int64) (CoinutOptionChainResponse, error) {
var result CoinutOptionChainResponse
params := make(map[string]interface{})
params["asset"] = asset
params["sec_type"] = secType
err := c.SendAuthenticatedHTTPRequest(COINUT_OPTION_CHAIN, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetPositionHistory(secType string, start, limit int) (CoinutPositionHistory, error) {
var result CoinutPositionHistory
params := make(map[string]interface{})
params["sec_type"] = secType
if start >= 0 {
params["start"] = start
}
if limit >= 0 {
params["limit"] = limit
}
err := c.SendAuthenticatedHTTPRequest(COINUT_POSITION_HISTORY, params, &result)
if err != nil {
return result, err
}
return result, nil
}
func (c *COINUT) GetOpenPosition(instrumentID int) ([]CoinutOpenPosition, error) {
type Response struct {
Positions []CoinutOpenPosition `json:"positions"`
}
var result Response
params := make(map[string]interface{})
params["inst_id"] = instrumentID
err := c.SendAuthenticatedHTTPRequest(COINUT_POSITION_OPEN, params, &result)
if err != nil {
return result.Positions, err
}
return result.Positions, nil
}
//to-do: user position update via websocket
func (c *COINUT) SendAuthenticatedHTTPRequest(apiRequest string, params map[string]interface{}, result interface{}) (err error) {
timestamp := time.Now().Unix()
payload := []byte("")
if params == nil {
params = map[string]interface{}{}
}
params["nonce"] = timestamp
params["request"] = apiRequest
payload, err = common.JSONEncode(params)
if err != nil {
return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request")
}
if c.Verbose {
log.Printf("Request JSON: %s\n", payload)
}
hmac := common.GetHMAC(common.HASH_SHA256, []byte(payload), []byte(c.APIKey))
headers := make(map[string]string)
headers["X-USER"] = c.ClientID
headers["X-SIGNATURE"] = common.HexEncodeToString(hmac)
headers["Content-Type"] = "application/json"
resp, err := common.SendHTTPRequest("POST", COINUT_API_URL, headers, bytes.NewBuffer(payload))
if c.Verbose {
log.Printf("Recieved raw: \n%s", resp)
}
genResp := CoinutGenericResponse{}
err = common.JSONDecode([]byte(resp), &genResp)
if err != nil {
return errors.New("Unable to JSON Unmarshal generic response.")
}
if genResp.Status[0] != "OK" {
return errors.New("Status is not OK.")
}
err = common.JSONDecode([]byte(resp), &result)
if err != nil {
return errors.New("Unable to JSON Unmarshal response.")
}
return nil
}

View File

@@ -0,0 +1,213 @@
package coinut
type CoinutGenericResponse struct {
Nonce int64 `json:"nonce"`
Reply string `json:"reply"`
Status []string `json:"status"`
TransID int64 `json:"trans_id"`
Timestamp int64 `json:"timestamp"`
}
type CoinutInstrumentBase struct {
Base string `json:"base"`
DecimalPlaces int `json:"decimal_places"`
InstID int `json:"inst_id"`
Quote string `json:"quote"`
}
type CoinutInstruments struct {
Instruments map[string][]CoinutInstrumentBase `json:"SPOT"`
}
type CoinutTicker struct {
HighestBuy float64 `json:"highest_buy,string"`
InstrumentID int `json:"inst_id"`
Last float64 `json:"last,string"`
LowestSell float64 `json:"lowest_sell,string"`
OpenInterest float64 `json:"open_interest,string"`
Timestamp float64 `json:"timestamp"`
TransID int64 `json:"trans_id"`
Volume float64 `json:"volume,string"`
Volume24 float64 `json:"volume24,string"`
}
type CoinutOrderbookBase struct {
Count int `json:"count"`
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,string"`
}
type CoinutOrderbook struct {
Buy []CoinutOrderbookBase `json:"buy"`
Sell []CoinutOrderbookBase `json:"sell"`
InstrumentID int `json:"inst_id"`
TotalBuy float64 `json:"total_buy,string"`
TotalSell float64 `json:"total_sell,string"`
TransID int64 `json:"trans_id"`
}
type CoinutTradeBase struct {
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,string"`
Side string `json:"side"`
Timestamp float64 `json:"timestamp"`
TransID int64 `json:"trans_id"`
}
type CoinutTrades struct {
Trades []CoinutTradeBase `json:"trades"`
}
type CoinutUserBalance struct {
BTC float64 `json:"btc,string"`
ETC float64 `json:"etc,string"`
ETH float64 `json:"eth,string"`
LTC float64 `json:"ltc,string"`
Equity float64 `json:"equity,string,string"`
InitialMargin float64 `json:"initial_margin,string"`
MaintenanceMargin float64 `json:"maintenance_margin,string"`
RealizedPL float64 `json:"realized_pl,string"`
TransID int64 `json:"trans_id"`
UnrealizedPL float64 `json:"unrealized_pl,string"`
}
type CoinutOrder struct {
InstrumentID int64 `json:"inst_id"`
Price float64 `json:"price,string"`
Quantity float64 `json:"qty,string"`
ClientOrderID int `json:"client_ord_id"`
Side string `json:"side,string"`
}
type CoinutOrderResponse struct {
OrderID int64 `json:"order_id"`
OpenQuantity float64 `json:"open_qty,string"`
Price float64 `json:"price,string"`
Quantity float64 `json:"qty,string"`
InstrumentID int64 `json:"inst_id"`
ClientOrderID int64 `json:"client_ord_id"`
Timestamp int64 `json:"timestamp"`
OrderPrice float64 `json:"order_price,string"`
Side string `json:"side"`
}
type CoinutCommission struct {
Currency string `json:"currency"`
Amount float64 `json:"amount,string"`
}
type CoinutOrderFilledResponse struct {
CoinutGenericResponse
Commission CoinutCommission `json:"commission"`
FillPrice float64 `json:"fill_price,string"`
FillQuantity float64 `json:"fill_qty,string"`
Order CoinutOrderResponse `json:"order"`
}
type CoinutOrderRejectResponse struct {
CoinutOrderResponse
Reasons []string `json:"reasons"`
}
type CoinutOrdersBase struct {
CoinutGenericResponse
CoinutOrderResponse
}
type CoinutOrdersResponse struct {
Data []CoinutOrdersBase
}
type CoinutCancelOrders struct {
InstrumentID int `json:"int"`
OrderID int64 `json:"order_id"`
}
type CoinutCancelOrdersResponse struct {
CoinutGenericResponse
Results []struct {
OrderID int64 `json:"order_id"`
Status string `json:"status"`
InstrumentID int `json:"inst_id"`
} `json:"results"`
}
type CoinutTradeHistory struct {
TotalNumber int64 `json:"total_number"`
Trades []CoinutOrderFilledResponse `json:"trades"`
}
type CoinutIndexTicker struct {
Asset string `json:"asset"`
Price float64 `json:"price,string"`
}
type CoinutOption struct {
HighestBuy float64 `json:"highest_buy,string"`
InstrumentID int `json:"inst_id"`
Last float64 `json:"last,string"`
LowestSell float64 `json:"lowest_sell,string"`
OpenInterest float64 `json:"open_interest,string"`
}
type CoinutOptionChainResponse struct {
ExpiryTime int64 `json:"expiry_time"`
SecurityType string `json:"sec_type"`
Asset string `json:"asset"`
Entries []struct {
Call CoinutOption `json:"call"`
Put CoinutOption `json:"put"`
Strike float64 `json:"strike,string"`
}
}
type CoinutOptionChainUpdate struct {
CoinutOption
CoinutGenericResponse
Asset string `json:"asset"`
ExpiryTime int64 `json:"expiry_time"`
SecurityType string `json:"sec_type"`
Volume float64 `json:"volume,string"`
}
type CoinutPositionHistory struct {
Positions []struct {
PositionID int `json:"position_id"`
Records []struct {
Commission CoinutCommission `json:"commission"`
FillPrice float64 `json:"fill_price,string,omitempty"`
TransactionID int `json:"trans_id"`
FillQuantity float64 `json:"fill_qty,omitempty"`
Position struct {
Commission CoinutCommission `json:"commission"`
Timestamp int64 `json:"timestamp"`
OpenPrice float64 `json:"open_price,string"`
RealizedPL float64 `json:"realized_pl,string"`
Quantity float64 `json:"qty,string"`
} `json:"position"`
AssetAtExpiry float64 `json:"asset_at_expiry,string,omitempty"`
} `json:"records"`
Instrument struct {
ExpiryTime int64 `json:"expiry_time"`
ContractSize float64 `json:"contract_size,string"`
ConversionRate float64 `json:"conversion_rate,string"`
OptionType string `json:"option_type"`
InstrumentID int `json:"inst_id"`
SecType string `json:"sec_type"`
Asset string `json:"asset"`
Strike float64 `json:"strike,string"`
} `json:"inst"`
OpenTimestamp int64 `json:"open_timestamp"`
} `json:"positions"`
TotalNumber int `json:"total_number"`
}
type CoinutOpenPosition struct {
PositionID int `json:"position_id"`
Commission CoinutCommission `json:"commission"`
OpenPrice float64 `json:"open_price,string"`
RealizedPL float64 `json:"realized_pl,string"`
Quantity float64 `json:"qty,string"`
OpenTimestamp int64 `json:"open_timestamp"`
InstrumentID int `json:"inst_id"`
}

View File

@@ -0,0 +1,60 @@
package coinut
import (
"log"
"net/http"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
)
const COINUT_WEBSOCKET_URL = "wss://wsapi.coinut.com"
func (c *COINUT) WebsocketClient() {
for c.Enabled && c.Websocket {
var Dialer websocket.Dialer
var err error
c.WebsocketConn, _, err = Dialer.Dial(c.WebsocketURL, http.Header{})
if err != nil {
log.Printf("%s Unable to connect to Websocket. Error: %s\n", c.Name, err)
continue
}
if c.Verbose {
log.Printf("%s Connected to Websocket.\n", c.Name)
}
err = c.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(`{"messageType": "hello_world"}`))
if err != nil {
log.Println(err)
return
}
for c.Enabled && c.Websocket {
msgType, resp, err := c.WebsocketConn.ReadMessage()
if err != nil {
log.Println(err)
break
}
switch msgType {
case websocket.TextMessage:
type MsgType struct {
MessageType string `json:"messageType"`
}
msgType := MsgType{}
err := common.JSONDecode(resp, &msgType)
if err != nil {
log.Println(err)
continue
}
log.Println(string(resp))
}
}
c.WebsocketConn.Close()
log.Printf("%s Websocket client disconnected.", c.Name)
}
}

View File

@@ -0,0 +1,129 @@
package coinut
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
func (c *COINUT) Start() {
go c.Run()
}
func (c *COINUT) Run() {
if c.Verbose {
log.Printf("%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket), COINUT_WEBSOCKET_URL)
log.Printf("%s polling delay: %ds.\n", c.GetName(), c.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", c.GetName(), len(c.EnabledPairs), c.EnabledPairs)
}
if c.Websocket {
go c.WebsocketClient()
}
exchangeProducts, err := c.GetInstruments()
if err != nil {
log.Printf("%s Failed to get available products.\n", c.GetName())
return
}
currencies := []string{}
c.InstrumentMap = make(map[string]int)
for x, y := range exchangeProducts.Instruments {
c.InstrumentMap[x] = y[0].InstID
currencies = append(currencies, x)
}
err = c.UpdateAvailableCurrencies(currencies)
if err != nil {
log.Printf("%s Failed to get config.\n", c.GetName())
}
for c.Enabled {
for _, x := range c.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := c.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("COINUT %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(c.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * c.RESTPollingDelay)
}
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the COINUT exchange
func (e *COINUT) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
/*
response.ExchangeName = e.GetName()
accountBalance, err := e.GetAccounts()
if err != nil {
return response, err
}
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.TotalValue = accountBalance[i].Available
exchangeCurrency.Hold = accountBalance[i].Hold
response.Currencies = append(response.Currencies, exchangeCurrency)
}
*/
return response, nil
}
func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(c.GetName(), p)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
tick, err := c.GetInstrumentTicker(c.InstrumentMap[p.Pair().String()])
if err != nil {
return ticker.TickerPrice{}, err
}
tickerPrice.Pair = p
tickerPrice.Volume = tick.Volume
tickerPrice.Last = tick.Last
tickerPrice.High = tick.HighestBuy
tickerPrice.Low = tick.LowestSell
ticker.ProcessTicker(c.GetName(), p, tickerPrice)
return tickerPrice, nil
}
func (c *COINUT) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(c.GetName(), p)
if err == nil {
return ob, nil
}
var orderBook orderbook.OrderbookBase
orderbookNew, err := c.GetInstrumentOrderbook(c.InstrumentMap[p.Pair().String()], 200)
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Buy {
orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Buy[x].Quantity, Price: orderbookNew.Buy[x].Price})
}
for x := range orderbookNew.Sell {
orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Sell[x].Quantity, Price: orderbookNew.Sell[x].Price})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(c.GetName(), p, orderBook)
return orderBook, nil
}