Coinut code impovements and websocket pair formatting fixes

This commit is contained in:
Adrian Gallagher
2019-09-24 18:16:42 +10:00
parent 132d2d03c2
commit 4c5b0a4aa1
8 changed files with 491 additions and 212 deletions

View File

@@ -285,10 +285,10 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() {
continue
}
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = "-"
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[i],
Currency: enabledCurrencies[j],
Channel: channels[i],
Currency: c.FormatExchangeCurrency(enabledCurrencies[j],
asset.Spot),
})
}
}
@@ -303,7 +303,8 @@ func (c *CoinbasePro) Subscribe(channelToSubscribe wshandler.WebsocketChannelSub
{
Name: channelToSubscribe.Channel,
ProductIDs: []string{
channelToSubscribe.Currency.String(),
c.FormatExchangeCurrency(channelToSubscribe.Currency,
asset.Spot).String(),
},
},
},
@@ -329,7 +330,8 @@ func (c *CoinbasePro) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelS
{
Name: channelToSubscribe.Channel,
ProductIDs: []string{
channelToSubscribe.Currency.String(),
c.FormatExchangeCurrency(channelToSubscribe.Currency,
asset.Spot).String(),
},
},
},

View File

@@ -159,14 +159,22 @@ func (c *CoinbasePro) Start(wg *sync.WaitGroup) {
// Run implements the coinbasepro wrapper
func (c *CoinbasePro) Run() {
if c.Verbose {
log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket.IsEnabled()), coinbaseproWebsocketURL)
log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n",
c.GetName(),
common.IsEnabled(c.Websocket.IsEnabled()),
coinbaseproWebsocketURL)
c.PrintEnabledPairs()
}
forceUpdate := false
if !common.StringDataContains(c.GetEnabledPairs(asset.Spot).Strings(), c.GetPairFormat(asset.Spot, false).Delimiter) ||
!common.StringDataContains(c.GetAvailablePairs(asset.Spot).Strings(), c.GetPairFormat(asset.Spot, false).Delimiter) {
enabledPairs := currency.NewPairsFromStrings([]string{fmt.Sprintf("BTC%vUSD", c.GetPairFormat(asset.Spot, false).Delimiter)})
delim := c.GetPairFormat(asset.Spot, false).Delimiter
if !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot,
true).Strings(), delim) ||
!common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot,
false).Strings(), delim) {
enabledPairs := currency.NewPairsFromStrings(
[]string{fmt.Sprintf("BTC%sUSD", delim)},
)
log.Warn(log.ExchangeSys,
"Enabled pairs for CoinbasePro reset due to config upgrade, please enable the ones you would like to use again")
forceUpdate = true
@@ -294,7 +302,8 @@ func (c *CoinbasePro) FetchOrderbook(p currency.Pair, assetType asset.Item) (ord
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := c.GetOrderbook(c.FormatExchangeCurrency(p, assetType).String(), 2)
orderbookNew, err := c.GetOrderbook(c.FormatExchangeCurrency(p,
assetType).String(), 2)
if err != nil {
return orderBook, err
}
@@ -324,8 +333,7 @@ func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType asset.Item) (or
// GetFundingHistory returns funding history, deposits and
// withdrawals
func (c *CoinbasePro) GetFundingHistory() ([]exchange.FundHistory, error) {
var fundHistory []exchange.FundHistory
return fundHistory, common.ErrFunctionNotSupported
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
@@ -352,7 +360,7 @@ func (c *CoinbasePro) SubmitOrder(order *exchange.OrderSubmission) (exchange.Sub
order.Amount,
order.Amount,
order.OrderSide.ToString(),
order.Pair.String(),
c.FormatExchangeCurrency(order.Pair, asset.Spot).String(),
"")
case exchange.LimitOrderType:
response, err = c.PlaceLimitOrder("",
@@ -361,7 +369,7 @@ func (c *CoinbasePro) SubmitOrder(order *exchange.OrderSubmission) (exchange.Sub
order.OrderSide.ToString(),
"",
"",
order.Pair.String(),
c.FormatExchangeCurrency(order.Pair, asset.Spot).String(),
"",
false)
default:

View File

@@ -42,11 +42,29 @@ const (
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 map[string]int
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
@@ -54,21 +72,19 @@ 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 int) (Ticker, error) {
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 int) (Orderbook, error) {
func (c *COINUT) GetInstrumentOrderbook(instrumentID, limit int64) (Orderbook, error) {
var result Orderbook
params := make(map[string]interface{})
params["inst_id"] = instrumentID
@@ -96,7 +112,7 @@ func (c *COINUT) GetUserBalance() (UserBalance, error) {
}
// NewOrder places a new order on the exchange
func (c *COINUT) NewOrder(instrumentID int, quantity, price float64, buy bool, orderID uint32) (interface{}, error) {
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
@@ -133,21 +149,20 @@ func (c *COINUT) NewOrders(orders []Order) ([]OrdersBase, error) {
}
// GetOpenOrders returns a list of open order and relevant information
func (c *COINUT) GetOpenOrders(instrumentID int) (GetOpenOrdersResponse, error) {
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 int) (bool, error) {
func (c *COINUT) CancelExistingOrder(instrumentID, orderID int64) (bool, error) {
var result GenericResponse
params := make(map[string]interface{})
type Request struct {
InstrumentID int `json:"inst_id"`
OrderID int `json:"order_id"`
InstrumentID int64 `json:"inst_id"`
OrderID int64 `json:"order_id"`
}
var entry = Request{
@@ -182,7 +197,7 @@ func (c *COINUT) CancelOrders(orders []CancelOrders) (CancelOrdersResponse, erro
}
// GetTradeHistory returns trade history for a specific instrument.
func (c *COINUT) GetTradeHistory(instrumentID, start, limit int) (TradeHistory, error) {
func (c *COINUT) GetTradeHistory(instrumentID, start, limit int64) (TradeHistory, error) {
var result TradeHistory
params := make(map[string]interface{})
params["inst_id"] = instrumentID

View File

@@ -76,9 +76,6 @@ func setupWSTestAuth(t *testing.T) {
if err != nil {
t.Error(err)
}
instrumentListByString = make(map[string]int64)
instrumentListByString[currency.NewPair(currency.LTC, currency.BTC).String()] = 1
wsSetupRan = true
}
@@ -89,6 +86,18 @@ func TestGetInstruments(t *testing.T) {
}
}
func TestSeedInstruments(t *testing.T) {
err := c.SeedInstruments()
if err != nil {
// No point checking the next condition
t.Fatal(err)
}
if len(c.instrumentMap.GetInstrumentIDs()) == 0 {
t.Error("instrument map hasn't been seeded")
}
}
func setFeeBuilder() *exchange.FeeBuilder {
return &exchange.FeeBuilder{
Amount: 1,
@@ -543,3 +552,89 @@ func TestWsAuthGetOpenOrders(t *testing.T) {
t.Error(err)
}
}
func TestCurrencyMapIsLoaded(t *testing.T) {
t.Parallel()
var i instrumentMap
if l := i.IsLoaded(); l {
t.Error("unexpected result")
}
i.Seed("BTCUSD", 1337)
if l := i.IsLoaded(); !l {
t.Error("unexpected result")
}
}
func TestCurrencyMapSeed(t *testing.T) {
t.Parallel()
var i instrumentMap
// Test non-seeded lookups
if id := i.LookupInstrument(1234); id != "" {
t.Error("unexpected result")
}
if id := i.LookupID("BLAH"); id != 0 {
t.Error("unexpected result")
}
// Test seeded lookups
i.Seed("BTCUSD", 1337)
if id := i.LookupID("BTCUSD"); id != 1337 {
t.Error("unexpected result")
}
if id := i.LookupInstrument(1337); id != "BTCUSD" {
t.Error("unexpected result")
}
// Test invalid lookups
if id := i.LookupInstrument(1234); id != "" {
t.Error("unexpected result")
}
if id := i.LookupID("BLAH"); id != 0 {
t.Error("unexpected result")
}
// Test seeding existing item
i.Seed("BTCUSD", 1234)
if id := i.LookupID("BTCUSD"); id != 1337 {
t.Error("unexpected result")
}
if id := i.LookupInstrument(1337); id != "BTCUSD" {
t.Error("unexpected result")
}
}
func TestCurrencyMapInstrumentIDs(t *testing.T) {
t.Parallel()
var i instrumentMap
if r := i.GetInstrumentIDs(); len(r) > 0 {
t.Error("non initialised instrument map shouldn't return any ids")
}
// Seed the instrument map
i.Seed("BTCUSD", 1234)
i.Seed("LTCUSD", 1337)
f := func(ids []int64, target int64) bool {
for x := range ids {
if ids[x] == target {
return true
}
}
return false
}
// Test 2 valid instruments and one invalid
ids := i.GetInstrumentIDs()
if r := f(ids, 1234); !r {
t.Error("unexpected result")
}
if r := f(ids, 1337); !r {
t.Error("unexpected result")
}
if r := f(ids, 4321); r {
t.Error("unexpected result")
}
}

View File

@@ -1,6 +1,9 @@
package coinut
import (
"strings"
"sync"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
)
@@ -17,7 +20,7 @@ type GenericResponse struct {
type InstrumentBase struct {
Base string `json:"base"`
DecimalPlaces int `json:"decimal_places"`
InstID int `json:"inst_id"`
InstID int64 `json:"inst_id"`
Quote string `json:"quote"`
}
@@ -665,3 +668,85 @@ type WsGetAccountBalanceResponse struct {
Status []string `json:"status"`
TransID int64 `json:"trans_id"`
}
type instrumentMap struct {
Instruments map[string]int64
Loaded bool
m sync.Mutex
}
// IsLoaded returns whether or not the instrument map has been seeded
func (i *instrumentMap) IsLoaded() bool {
i.m.Lock()
defer i.m.Unlock()
return i.Loaded
}
// Seed seeds the instrument map
func (i *instrumentMap) Seed(currency string, id int64) {
i.m.Lock()
defer i.m.Unlock()
if !i.Loaded {
i.Instruments = make(map[string]int64)
}
// check to see if the instrument already exists
_, ok := i.Instruments[currency]
if ok {
return
}
i.Instruments[currency] = id
i.Loaded = true
}
// LookupInstrument looks up an instrument based on an id
func (i *instrumentMap) LookupInstrument(id int64) string {
i.m.Lock()
defer i.m.Unlock()
if !i.Loaded {
return ""
}
for k, v := range i.Instruments {
if v == id {
return k
}
}
return ""
}
// LookupID looks up an ID based on a string
func (i *instrumentMap) LookupID(currency string) int64 {
i.m.Lock()
defer i.m.Unlock()
if !i.Loaded {
return 0
}
for k, v := range i.Instruments {
if strings.EqualFold(currency, k) {
return v
}
}
return 0
}
// GetInstrumentIDs returns a list of IDs
func (i *instrumentMap) GetInstrumentIDs() []int64 {
i.m.Lock()
defer i.m.Unlock()
if !i.Loaded {
return nil
}
var instruments []int64
for _, x := range i.Instruments {
instruments = append(instruments, x)
}
return instruments
}

View File

@@ -18,14 +18,15 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
)
const coinutWebsocketURL = "wss://wsapi.coinut.com"
const coinutWebsocketRateLimit = 30
const (
coinutWebsocketURL = "wss://wsapi.coinut.com"
coinutWebsocketRateLimit = 30
)
var nNonce map[int64]string
var channels map[string]chan []byte
var instrumentListByString map[string]int64
var instrumentListByCode map[int64]string
var populatedList bool
var (
channels map[string]chan []byte
wsInstrumentMap instrumentMap
)
// NOTE for speed considerations
// wss://wsapi-as.coinut.com
@@ -44,14 +45,11 @@ func (c *COINUT) WsConnect() error {
}
go c.WsHandleData()
if !populatedList {
instrumentListByString = make(map[string]int64)
instrumentListByCode = make(map[int64]string)
if !wsInstrumentMap.IsLoaded() {
err = c.WsSetInstrumentList()
if err != nil {
return err
}
populatedList = true
}
c.wsAuthenticate()
c.GenerateDefaultSubscriptions()
@@ -137,7 +135,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
c.Websocket.DataHandler <- err
return
}
currencyPair := instrumentListByCode[ticker.InstID]
currencyPair := wsInstrumentMap.LookupInstrument(ticker.InstID)
c.Websocket.DataHandler <- wshandler.TickerData{
Exchange: c.Name,
Volume: ticker.Volume,
@@ -147,7 +145,9 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
Last: ticker.Last,
Timestamp: time.Unix(0, ticker.Timestamp),
AssetType: asset.Spot,
Pair: currency.NewPairFromString(currencyPair),
Pair: currency.NewPairFromFormattedPairs(currencyPair,
c.GetEnabledPairs(asset.Spot),
c.GetPairFormat(asset.Spot, true)),
}
case "inst_order_book":
@@ -162,11 +162,13 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
c.Websocket.DataHandler <- err
return
}
currencyPair := instrumentListByCode[orderbooksnapshot.InstID]
currencyPair := wsInstrumentMap.LookupInstrument(orderbooksnapshot.InstID)
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: c.GetName(),
Asset: asset.Spot,
Pair: currency.NewPairFromString(currencyPair),
Pair: currency.NewPairFromFormattedPairs(currencyPair,
c.GetEnabledPairs(asset.Spot),
c.GetPairFormat(asset.Spot, true)),
}
case "inst_order_book_update":
var orderbookUpdate WsOrderbookUpdate
@@ -180,11 +182,13 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
c.Websocket.DataHandler <- err
return
}
currencyPair := instrumentListByCode[orderbookUpdate.InstID]
currencyPair := wsInstrumentMap.LookupInstrument(orderbookUpdate.InstID)
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: c.GetName(),
Asset: asset.Spot,
Pair: currency.NewPairFromString(currencyPair),
Pair: currency.NewPairFromFormattedPairs(currencyPair,
c.GetEnabledPairs(asset.Spot),
c.GetPairFormat(asset.Spot, true)),
}
case "inst_trade":
var tradeSnap WsTradeSnapshot
@@ -201,14 +205,16 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
c.Websocket.DataHandler <- err
return
}
currencyPair := instrumentListByCode[tradeUpdate.InstID]
currencyPair := wsInstrumentMap.LookupInstrument(tradeUpdate.InstID)
c.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: time.Unix(tradeUpdate.Timestamp, 0),
CurrencyPair: currency.NewPairFromString(currencyPair),
AssetType: asset.Spot,
Exchange: c.GetName(),
Price: tradeUpdate.Price,
Side: tradeUpdate.Side,
Timestamp: time.Unix(tradeUpdate.Timestamp, 0),
CurrencyPair: currency.NewPairFromFormattedPairs(currencyPair,
c.GetEnabledPairs(asset.Spot),
c.GetPairFormat(asset.Spot, true)),
AssetType: asset.Spot,
Exchange: c.GetName(),
Price: tradeUpdate.Price,
Side: tradeUpdate.Side,
}
default:
if incoming.Nonce > 0 {
@@ -247,11 +253,10 @@ func (c *COINUT) WsSetInstrumentList() error {
return err
}
for curr, data := range list.Spot {
instrumentListByString[curr] = data[0].InstID
instrumentListByCode[data[0].InstID] = curr
wsInstrumentMap.Seed(curr, data[0].InstID)
}
if len(instrumentListByString) == 0 || len(instrumentListByCode) == 0 {
return errors.New("instrument lists failed to populate")
if len(wsInstrumentMap.GetInstrumentIDs()) == 0 {
return errors.New("instrument list failed to populate")
}
return nil
}
@@ -277,7 +282,11 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error {
var newOrderBook orderbook.Base
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.Pair = currency.NewPairFromString(instrumentListByCode[ob.InstID])
newOrderBook.Pair = currency.NewPairFromFormattedPairs(
wsInstrumentMap.LookupInstrument(ob.InstID),
c.GetEnabledPairs(asset.Spot),
c.GetPairFormat(asset.Spot, true),
)
newOrderBook.AssetType = asset.Spot
return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
@@ -285,7 +294,11 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error {
// WsProcessOrderbookUpdate process an orderbook update
func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error {
p := currency.NewPairFromString(instrumentListByCode[update.InstID])
p := currency.NewPairFromFormattedPairs(
wsInstrumentMap.LookupInstrument(update.InstID),
c.GetEnabledPairs(asset.Spot),
c.GetPairFormat(asset.Spot, true),
)
bufferUpdate := &wsorderbook.WebsocketOrderbookUpdate{
CurrencyPair: p,
UpdateID: update.TransID,
@@ -318,8 +331,9 @@ func (c *COINUT) GenerateDefaultSubscriptions() {
// Subscribe sends a websocket message to receive data from the channel
func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := wsRequest{
Request: channelToSubscribe.Channel,
InstID: instrumentListByString[channelToSubscribe.Currency.String()],
Request: channelToSubscribe.Channel,
InstID: wsInstrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency,
asset.Spot).String()),
Subscribe: true,
Nonce: c.WebsocketConn.GenerateMessageID(false),
}
@@ -329,8 +343,9 @@ func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscrip
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (c *COINUT) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
subscribe := wsRequest{
Request: channelToSubscribe.Channel,
InstID: instrumentListByString[channelToSubscribe.Currency.String()],
Request: channelToSubscribe.Channel,
InstID: wsInstrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency,
asset.Spot).String()),
Subscribe: false,
Nonce: c.WebsocketConn.GenerateMessageID(false),
}
@@ -419,7 +434,7 @@ func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) (*WsStandardOrder
var orderSubmissionRequest WsSubmitOrderRequest
orderSubmissionRequest.Request = "new_order"
orderSubmissionRequest.Nonce = c.WebsocketConn.GenerateMessageID(false)
orderSubmissionRequest.InstID = instrumentListByString[curr]
orderSubmissionRequest.InstID = wsInstrumentMap.LookupID(curr)
orderSubmissionRequest.Qty = order.Amount
orderSubmissionRequest.Price = order.Price
orderSubmissionRequest.Side = string(order.Side)
@@ -530,7 +545,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO
Qty: orders[i].Amount,
Price: orders[i].Price,
Side: string(orders[i].Side),
InstID: instrumentListByString[curr],
InstID: wsInstrumentMap.LookupID(curr),
ClientOrdID: i + 1,
})
}
@@ -567,7 +582,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]WsStandardO
if len(standardOrder.Reasons) > 0 && standardOrder.Reasons[0] != "" {
errors = append(errors, fmt.Errorf("%v order submission failed for currency %v and orderID %v, message %v ",
c.Name,
instrumentListByCode[standardOrder.InstID],
wsInstrumentMap.LookupInstrument(standardOrder.InstID),
standardOrder.OrderID,
standardOrder.Reasons[0]))
@@ -587,7 +602,7 @@ func (c *COINUT) wsGetOpenOrders(p currency.Pair) error {
var openOrdersRequest WsGetOpenOrdersRequest
openOrdersRequest.Request = "user_open_orders"
openOrdersRequest.Nonce = c.WebsocketConn.GenerateMessageID(false)
openOrdersRequest.InstID = instrumentListByString[curr]
openOrdersRequest.InstID = wsInstrumentMap.LookupID(curr)
resp, err := c.WebsocketConn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest)
if err != nil {
@@ -613,7 +628,7 @@ func (c *COINUT) wsCancelOrder(cancellation WsCancelOrderParameters) error {
currency := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String()
var cancellationRequest WsCancelOrderRequest
cancellationRequest.Request = "cancel_order"
cancellationRequest.InstID = instrumentListByString[currency]
cancellationRequest.InstID = wsInstrumentMap.LookupID(currency)
cancellationRequest.OrderID = cancellation.OrderID
cancellationRequest.Nonce = c.WebsocketConn.GenerateMessageID(false)
@@ -645,7 +660,7 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCan
for i := range cancellations {
currency := c.FormatExchangeCurrency(cancellations[i].Currency, asset.Spot).String()
cancelOrderRequest.Entries = append(cancelOrderRequest.Entries, WsCancelOrdersRequestEntry{
InstID: instrumentListByString[currency],
InstID: wsInstrumentMap.LookupID(currency),
OrderID: cancellations[i].OrderID,
})
}
@@ -668,7 +683,7 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*WsCan
if response.Results[i].Status != "OK" {
errors = append(errors, fmt.Errorf("%v order cancellation failed for currency %v and orderID %v, message %v",
c.Name,
instrumentListByCode[response.Results[i].InstID],
wsInstrumentMap.LookupInstrument(response.Results[i].InstID),
response.Results[i].OrderID,
response.Results[i].Status))
}
@@ -683,7 +698,7 @@ func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error {
curr := c.FormatExchangeCurrency(p, asset.Spot).String()
var request WsTradeHistoryRequest
request.Request = "trade_history"
request.InstID = instrumentListByString[curr]
request.InstID = wsInstrumentMap.LookupID(curr)
request.Nonce = c.WebsocketConn.GenerateMessageID(false)
request.Start = start
request.Limit = limit

View File

@@ -1,6 +1,7 @@
package coinut
import (
"errors"
"fmt"
"strconv"
"strings"
@@ -60,6 +61,7 @@ func (c *COINUT) SetDefaults() {
},
ConfigFormat: &currency.PairFormat{
Uppercase: true,
Delimiter: "-",
},
}
@@ -162,11 +164,30 @@ func (c *COINUT) Run() {
c.PrintEnabledPairs()
}
if !c.GetEnabledFeatures().AutoPairUpdates {
forceUpdate := false
delim := c.GetPairFormat(asset.Spot, false).Delimiter
if !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot,
true).Strings(), delim) ||
!common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot,
false).Strings(), delim) {
enabledPairs := currency.NewPairsFromStrings(
[]string{fmt.Sprintf("LTC%sUSDT", delim)},
)
log.Warn(log.ExchangeSys,
"Enabled pairs for Coinut reset due to config upgrade, please enable the ones you would like to use again")
forceUpdate = true
err := c.UpdatePairs(enabledPairs, asset.Spot, true, true)
if err != nil {
log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", c.Name, err)
}
}
if !c.GetEnabledFeatures().AutoPairUpdates && !forceUpdate {
return
}
err := c.UpdateTradablePairs(false)
err := c.UpdateTradablePairs(forceUpdate)
if err != nil {
log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", c.Name, err)
}
@@ -180,10 +201,10 @@ func (c *COINUT) FetchTradablePairs(asset asset.Item) ([]string, error) {
}
var pairs []string
c.InstrumentMap = make(map[string]int)
for x, y := range i.Instruments {
c.InstrumentMap[x] = y[0].InstID
pairs = append(pairs, x)
for _, y := range i.Instruments {
c.instrumentMap.Seed(y[0].Base+y[0].Quote, y[0].InstID)
p := y[0].Base + c.GetPairFormat(asset, false).Delimiter + y[0].Quote
pairs = append(pairs, p)
}
return pairs, nil
@@ -197,7 +218,8 @@ func (c *COINUT) UpdateTradablePairs(forceUpdate bool) error {
return err
}
return c.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate)
return c.UpdatePairs(currency.NewPairsFromStrings(pairs),
asset.Spot, false, forceUpdate)
}
// GetAccountInfo retrieves balances for all enabled currencies for the
@@ -278,7 +300,21 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) {
// UpdateTicker updates and returns the ticker for a currency pair
func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := c.GetInstrumentTicker(c.InstrumentMap[c.FormatExchangeCurrency(p, assetType).String()])
if !c.instrumentMap.IsLoaded() {
err := c.SeedInstruments()
if err != nil {
return tickerPrice, err
}
}
instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p,
assetType).String())
if instID == 0 {
return tickerPrice, errors.New("unable to lookup instrument ID")
}
tick, err := c.GetInstrumentTicker(instID)
if err != nil {
return tickerPrice, err
}
@@ -320,7 +356,21 @@ func (c *COINUT) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderboo
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := c.GetInstrumentOrderbook(c.InstrumentMap[p.String()], 200)
if !c.instrumentMap.IsLoaded() {
err := c.SeedInstruments()
if err != nil {
return orderBook, err
}
}
instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p,
assetType).String())
if instID == 0 {
return orderBook, errLookupInstrumentID
}
orderbookNew, err := c.GetInstrumentOrderbook(instID, 200)
if err != nil {
return orderBook, err
}
@@ -378,14 +428,18 @@ func (c *COINUT) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOr
clientIDUint := uint32(clientIDInt)
// Need to get the ID of the currency sent
instruments, err := c.GetInstruments()
if err != nil {
return submitOrderResponse, err
if !c.instrumentMap.IsLoaded() {
err = c.SeedInstruments()
if err != nil {
return submitOrderResponse, err
}
}
currencyArray := instruments.Instruments[order.Pair.String()]
currencyID := currencyArray[0].InstID
currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(order.Pair,
asset.Spot).String())
if currencyID == 0 {
return submitOrderResponse, errLookupInstrumentID
}
switch order.OrderType {
case exchange.LimitOrderType:
@@ -425,23 +479,25 @@ func (c *COINUT) ModifyOrder(action *exchange.ModifyOrder) (string, error) {
// CancelOrder cancels an order by its corresponding ID number
func (c *COINUT) CancelOrder(order *exchange.OrderCancellation) error {
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
if err != nil {
return err
}
// Need to get the ID of the currency sent
instruments, err := c.GetInstruments()
if err != nil {
return err
if !c.instrumentMap.IsLoaded() {
err = c.SeedInstruments()
if err != nil {
return err
}
}
currencyArray := instruments.Instruments[c.FormatExchangeCurrency(order.CurrencyPair,
order.AssetType).String()]
currencyID := currencyArray[0].InstID
_, err = c.CancelExistingOrder(currencyID, int(orderIDInt))
currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(
order.CurrencyPair,
asset.Spot).String(),
)
if currencyID == 0 {
return errLookupInstrumentID
}
_, err = c.CancelExistingOrder(currencyID, orderIDInt)
return err
}
@@ -454,22 +510,22 @@ func (c *COINUT) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel
cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{
OrderStatus: make(map[string]string),
}
instruments, err := c.GetInstruments()
if err != nil {
return cancelAllOrdersResponse, err
if !c.instrumentMap.IsLoaded() {
err := c.SeedInstruments()
if err != nil {
return cancelAllOrdersResponse, err
}
}
var allTheOrders []OrderResponse
for _, allInstrumentData := range instruments.Instruments {
for _, instrumentData := range allInstrumentData {
openOrders, err := c.GetOpenOrders(instrumentData.InstID)
if err != nil {
return cancelAllOrdersResponse, err
}
allTheOrders = append(allTheOrders, openOrders.Orders...)
ids := c.instrumentMap.GetInstrumentIDs()
for x := range ids {
openOrders, err := c.GetOpenOrders(ids[x])
if err != nil {
return cancelAllOrdersResponse, err
}
allTheOrders = append(allTheOrders, openOrders.Orders...)
}
var allTheOrdersToCancel []CancelOrders
@@ -499,8 +555,7 @@ func (c *COINUT) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel
// GetOrderInfo returns information on a current open order
func (c *COINUT) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
return exchange.OrderDetail{}, common.ErrNotYetImplemented
}
// GetDepositAddress returns a deposit address for a specified currency
@@ -542,51 +597,51 @@ func (c *COINUT) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error)
// GetActiveOrders retrieves any orders that are active/open
func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
instruments, err := c.GetInstruments()
if err != nil {
return nil, err
}
var allTheOrders []OrderResponse
for instrument, allInstrumentData := range instruments.Instruments {
for _, instrumentData := range allInstrumentData {
for _, currency := range getOrdersRequest.Currencies {
currStr := fmt.Sprintf("%v%v%v",
currency.Base.String(),
c.CurrencyPairs.Get(asset.Spot).ConfigFormat.Delimiter,
currency.Quote.String())
if strings.EqualFold(currStr, instrument) {
openOrders, err := c.GetOpenOrders(instrumentData.InstID)
if err != nil {
return nil, err
}
allTheOrders = append(allTheOrders, openOrders.Orders...)
continue
}
}
if !c.instrumentMap.IsLoaded() {
err := c.SeedInstruments()
if err != nil {
return nil, err
}
}
var instrumentsToUse []int64
if len(getOrdersRequest.Currencies) > 0 {
for x := range getOrdersRequest.Currencies {
currency := c.FormatExchangeCurrency(getOrdersRequest.Currencies[x],
asset.Spot).String()
instrumentsToUse = append(instrumentsToUse,
c.instrumentMap.LookupID(currency))
}
} else {
instrumentsToUse = c.instrumentMap.GetInstrumentIDs()
}
if len(instrumentsToUse) == 0 {
return nil, errors.New("no instrument IDs to use")
}
var orders []exchange.OrderDetail
for _, order := range allTheOrders {
for instrument, allInstrumentData := range instruments.Instruments {
for _, instrumentData := range allInstrumentData {
if instrumentData.InstID == int(order.InstrumentID) {
currPair := currency.NewPairDelimiter(instrument, "")
orderSide := exchange.OrderSide(strings.ToUpper(order.Side))
orderDate := time.Unix(order.Timestamp, 0)
orders = append(orders, exchange.OrderDetail{
ID: strconv.FormatInt(order.OrderID, 10),
Amount: order.Quantity,
Price: order.Price,
Exchange: c.Name,
OrderSide: orderSide,
OrderDate: orderDate,
CurrencyPair: currPair,
})
}
}
for x := range instrumentsToUse {
openOrders, err := c.GetOpenOrders(instrumentsToUse[x])
if err != nil {
return nil, err
}
for y := range openOrders.Orders {
curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x])
p := currency.NewPairFromFormattedPairs(curr,
c.GetEnabledPairs(asset.Spot),
c.GetPairFormat(asset.Spot, true))
orderSide := exchange.OrderSide(strings.ToUpper(openOrders.Orders[y].Side))
orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0)
orders = append(orders, exchange.OrderDetail{
ID: strconv.FormatInt(openOrders.Orders[y].OrderID, 10),
Amount: openOrders.Orders[y].Quantity,
Price: openOrders.Orders[y].Price,
Exchange: c.Name,
OrderSide: orderSide,
OrderDate: orderDate,
CurrencyPair: p,
})
}
}
@@ -598,56 +653,60 @@ func (c *COINUT) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([
// GetOrderHistory retrieves account order information
// Can Limit response to specific order status
func (c *COINUT) GetOrderHistory(getOrdersRequest *exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
instruments, err := c.GetInstruments()
if err != nil {
return nil, err
}
var allTheOrders []OrderFilledResponse
for instrument, allInstrumentData := range instruments.Instruments {
for _, instrumentData := range allInstrumentData {
for _, currency := range getOrdersRequest.Currencies {
currStr := fmt.Sprintf("%v%v",
currency.Base.String(), currency.Quote.String())
if strings.EqualFold(currStr, instrument) {
orders, err := c.GetTradeHistory(instrumentData.InstID, -1, -1)
if err != nil {
return nil, err
}
allTheOrders = append(allTheOrders, orders.Trades...)
continue
}
}
if !c.instrumentMap.IsLoaded() {
err := c.SeedInstruments()
if err != nil {
return nil, err
}
}
var orders []exchange.OrderDetail
for i := range allTheOrders {
for instrument, allInstrumentData := range instruments.Instruments {
for j := range allInstrumentData {
if allInstrumentData[j].InstID == int(allTheOrders[i].Order.InstrumentID) {
currPair := currency.NewPairDelimiter(instrument, "")
orderSide := exchange.OrderSide(strings.ToUpper(allTheOrders[i].Order.Side))
orderDate := time.Unix(allTheOrders[i].Order.Timestamp, 0)
orders = append(orders, exchange.OrderDetail{
ID: strconv.FormatInt(allTheOrders[i].Order.OrderID, 10),
Amount: allTheOrders[i].Order.Quantity,
Price: allTheOrders[i].Order.Price,
Exchange: c.Name,
OrderSide: orderSide,
OrderDate: orderDate,
CurrencyPair: currPair,
})
}
}
var instrumentsToUse []int64
if len(getOrdersRequest.Currencies) > 0 {
for x := range getOrdersRequest.Currencies {
currency := c.FormatExchangeCurrency(getOrdersRequest.Currencies[x],
asset.Spot).String()
instrumentsToUse = append(instrumentsToUse,
c.instrumentMap.LookupID(currency))
}
} else {
instrumentsToUse = c.instrumentMap.GetInstrumentIDs()
}
exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks,
if len(instrumentsToUse) == 0 {
return nil, errors.New("no instrument IDs to use")
}
var allOrders []exchange.OrderDetail
for x := range instrumentsToUse {
orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1)
if err != nil {
return nil, err
}
for y := range orders.Trades {
curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x])
p := currency.NewPairFromFormattedPairs(curr,
c.GetEnabledPairs(asset.Spot),
c.GetPairFormat(asset.Spot, true))
orderSide := exchange.OrderSide(
strings.ToUpper(orders.Trades[y].Order.Side))
orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0)
allOrders = append(allOrders, exchange.OrderDetail{
ID: strconv.FormatInt(orders.Trades[y].Order.OrderID, 10),
Amount: orders.Trades[y].Order.Quantity,
Price: orders.Trades[y].Order.Price,
Exchange: c.Name,
OrderSide: orderSide,
OrderDate: orderDate,
CurrencyPair: p,
})
}
}
exchange.FilterOrdersByTickRange(&allOrders, getOrdersRequest.StartTicks,
getOrdersRequest.EndTicks)
exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
return orders, nil
exchange.FilterOrdersBySide(&allOrders, getOrdersRequest.OrderSide)
return allOrders, nil
}
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe

View File

@@ -124,20 +124,19 @@ func (o *OKCoin) Run() {
log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL)
}
if o.Config.CurrencyPairs.ConfigFormat.Delimiter != o.CurrencyPairs.ConfigFormat.Delimiter {
o.Config.CurrencyPairs.ConfigFormat.Delimiter = o.CurrencyPairs.ConfigFormat.Delimiter
}
if o.Config.CurrencyPairs.RequestFormat.Uppercase != o.CurrencyPairs.RequestFormat.Uppercase {
o.Config.CurrencyPairs.RequestFormat.Uppercase = true
}
if o.Config.CurrencyPairs.RequestFormat.Delimiter != o.CurrencyPairs.RequestFormat.Delimiter {
o.Config.CurrencyPairs.RequestFormat.Delimiter = o.CurrencyPairs.RequestFormat.Delimiter
}
if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(), o.CurrencyPairs.RequestFormat.Delimiter) {
enabledPairs := currency.NewPairsFromStrings([]string{"BTC-USD"})
forceUpdate := false
delim := o.GetPairFormat(asset.Spot, false).Delimiter
if !common.StringDataContains(o.CurrencyPairs.GetPairs(asset.Spot,
true).Strings(), delim) ||
!common.StringDataContains(o.CurrencyPairs.GetPairs(asset.Spot,
false).Strings(), delim) {
enabledPairs := currency.NewPairsFromStrings([]string{
fmt.Sprintf("BTC%sUSD", delim),
})
log.Warnf(log.ExchangeSys,
"Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.", o.Name)
"Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.\n",
o.Name)
forceUpdate = true
err := o.UpdatePairs(enabledPairs, asset.Spot, true, true)
if err != nil {
@@ -146,11 +145,11 @@ func (o *OKCoin) Run() {
}
}
if !o.GetEnabledFeatures().AutoPairUpdates {
if !o.GetEnabledFeatures().AutoPairUpdates && !forceUpdate {
return
}
err := o.UpdateTradablePairs(false)
err := o.UpdateTradablePairs(forceUpdate)
if err != nil {
log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", o.Name, err)
}
@@ -165,7 +164,8 @@ func (o *OKCoin) FetchTradablePairs(asset asset.Item) ([]string, error) {
var pairs []string
for x := range prods {
pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency, o.GetPairFormat(asset, false).Delimiter, prods[x].QuoteCurrency))
pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency,
o.GetPairFormat(asset, false).Delimiter, prods[x].QuoteCurrency))
}
return pairs, nil