mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-17 23:16:52 +00:00
Engine QA (#367)
* Improved error message when no config is set on startup * Change inccorect error wording * bump Bitfinex websocket orderbook return length to max * temporary fix of incorrect orderbook updates, limit to bid and ask len of 100, will be extended later if needed * Fixed issue in binance websocket that appended 0 volume bid/ask items * Fix panic when unmarshalling an empty pair from config * Add get pair asset method for exchange base Fix Bitmex orderbook stream Unbuffer Bitmex orderbook stream * force syncer to update ticker instead of fetch, which allows a stream * Fix websocket last price for coinbasepro * fix websocket ticker for coinut * Fix websocket orderbook stream Huobi * increase orderbook depth REST for Huobi * Fix websocket support and ensure data integrity * Fix time parsing issue after error checks * check error, only process enabled currency pairs, signal websocket data processing * expanded websocket functionality for okgroup * Add logic to not process zero length slice for orderbooks * fix websocket ticker only updating enabled and individual book updates * ZB fixes to order submission/retrieval/cancellation w/ general fixes * Quiet unnecessary warning * updated config entry values for REST and websocket (initial hack until I come up with a better solution for asset types) * Ch GetName function to field access modifyer & rm useless code * Add in error I missed * Nits addressed * some more fixes * Turned kraken default websocket to true and some small changes * fixes linter issues * Ensured okgroup books and sent update through to datahandler. Zb update as well. * Add test case to get asset type from pair * Add test for pairs unmarshal * Add testing and addressed nits * FIX linter issue * Addressed Gees nits * Thanks glorious spotter * more nitorinos * Addres even more nits * Add stringerino 4000 * Fix for panic cause by sort slice out of range, also nits addressed * fix linter issues * Changed from function to field access * Changed from function to field access * fix for orderbook update panic, removes quick fix - caused by sync item fetching through same protocol * Add new test and update random generator * pass in invalid string to future ob fetching, due to futures contract expire and a http 400 error is returned
This commit is contained in:
committed by
Adrian Gallagher
parent
e2c349424f
commit
22ff33cd54
@@ -1433,7 +1433,8 @@ func GetFilePath(file string) (string, error) {
|
||||
return newDirs[0], nil
|
||||
}
|
||||
|
||||
return "", errors.New("config default file path error")
|
||||
return "", fmt.Errorf("config.json file not found in %s, please follow README.md in root dir for config generation",
|
||||
newDir)
|
||||
}
|
||||
|
||||
// ReadConfig verifies and checks for encryption and verifies the unencrypted
|
||||
|
||||
@@ -73,6 +73,11 @@ func (p *Pairs) UnmarshalJSON(d []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// If no pairs enabled in config just continue
|
||||
if pairs == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var allThePairs Pairs
|
||||
for _, data := range strings.Split(pairs, ",") {
|
||||
allThePairs = append(allThePairs, NewPairFromString(data))
|
||||
|
||||
@@ -68,13 +68,28 @@ func TestPairsFormat(t *testing.T) {
|
||||
|
||||
func TestPairsUnmarshalJSON(t *testing.T) {
|
||||
var unmarshalHere Pairs
|
||||
configPairs := "btc_usd,btc_aud,btc_ltc"
|
||||
|
||||
configPairs := ""
|
||||
encoded, err := common.JSONEncode(configPairs)
|
||||
if err != nil {
|
||||
t.Fatal("Pairs UnmarshalJSON() error", err)
|
||||
}
|
||||
|
||||
err = common.JSONDecode([]byte{1, 3, 3, 7}, &unmarshalHere)
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
|
||||
err = common.JSONDecode(encoded, &unmarshalHere)
|
||||
if err != nil {
|
||||
t.Fatal("Pairs UnmarshalJSON() error", err)
|
||||
}
|
||||
|
||||
configPairs = "btc_usd,btc_aud,btc_ltc"
|
||||
encoded, err = common.JSONEncode(configPairs)
|
||||
if err != nil {
|
||||
t.Fatal("Pairs UnmarshalJSON() error", err)
|
||||
}
|
||||
|
||||
err = common.JSONDecode(encoded, &unmarshalHere)
|
||||
if err != nil {
|
||||
t.Fatal("Pairs UnmarshalJSON() error", err)
|
||||
|
||||
@@ -400,7 +400,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() {
|
||||
result, err = Bot.Exchanges[x].FetchTicker(c.Pair, c.AssetType)
|
||||
}
|
||||
} else {
|
||||
result, err = Bot.Exchanges[x].FetchTicker(c.Pair, c.AssetType)
|
||||
result, err = Bot.Exchanges[x].UpdateTicker(c.Pair, c.AssetType)
|
||||
}
|
||||
printTickerSummary(&result, c.Pair, c.AssetType, exchangeName, err)
|
||||
if err == nil {
|
||||
|
||||
@@ -92,13 +92,13 @@ type DepthUpdateParams []struct {
|
||||
|
||||
// WebsocketDepthStream is the difference for the update depth stream
|
||||
type WebsocketDepthStream struct {
|
||||
Event string `json:"e"`
|
||||
Timestamp int64 `json:"E"`
|
||||
Pair string `json:"s"`
|
||||
FirstUpdateID int64 `json:"U"`
|
||||
LastUpdateID int64 `json:"u"`
|
||||
UpdateBids []interface{} `json:"b"`
|
||||
UpdateAsks []interface{} `json:"a"`
|
||||
Event string `json:"e"`
|
||||
Timestamp int64 `json:"E"`
|
||||
Pair string `json:"s"`
|
||||
FirstUpdateID int64 `json:"U"`
|
||||
LastUpdateID int64 `json:"u"`
|
||||
UpdateBids [][]interface{} `json:"b"`
|
||||
UpdateAsks [][]interface{} `json:"a"`
|
||||
}
|
||||
|
||||
// RecentTradeRequestParams represents Klines request data.
|
||||
|
||||
@@ -236,12 +236,16 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error {
|
||||
}
|
||||
|
||||
for i := range orderbookNew.Bids {
|
||||
newOrderBook.Bids = append(newOrderBook.Bids,
|
||||
orderbook.Item{Amount: orderbookNew.Bids[i].Quantity, Price: orderbookNew.Bids[i].Price})
|
||||
newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{
|
||||
Amount: orderbookNew.Bids[i].Quantity,
|
||||
Price: orderbookNew.Bids[i].Price,
|
||||
})
|
||||
}
|
||||
for i := range orderbookNew.Asks {
|
||||
newOrderBook.Asks = append(newOrderBook.Asks,
|
||||
orderbook.Item{Amount: orderbookNew.Asks[i].Quantity, Price: orderbookNew.Asks[i].Price})
|
||||
newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{
|
||||
Amount: orderbookNew.Asks[i].Quantity,
|
||||
Price: orderbookNew.Asks[i].Price,
|
||||
})
|
||||
}
|
||||
|
||||
newOrderBook.LastUpdated = time.Unix(orderbookNew.LastUpdateID, 0)
|
||||
@@ -256,38 +260,38 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error {
|
||||
func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error {
|
||||
var updateBid, updateAsk []orderbook.Item
|
||||
for i := range wsdp.UpdateBids {
|
||||
var priceToBeUpdated orderbook.Item
|
||||
for i, bids := range wsdp.UpdateBids[i].([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
priceToBeUpdated.Price, _ = strconv.ParseFloat(bids.(string), 64)
|
||||
case 1:
|
||||
priceToBeUpdated.Amount, _ = strconv.ParseFloat(bids.(string), 64)
|
||||
}
|
||||
p, err := strconv.ParseFloat(wsdp.UpdateBids[i][0].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateBid = append(updateBid, priceToBeUpdated)
|
||||
a, err := strconv.ParseFloat(wsdp.UpdateBids[i][1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateBid = append(updateBid, orderbook.Item{Price: p, Amount: a})
|
||||
}
|
||||
|
||||
for i := range wsdp.UpdateAsks {
|
||||
var priceToBeUpdated orderbook.Item
|
||||
for i, asks := range wsdp.UpdateAsks[i].([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
priceToBeUpdated.Price, _ = strconv.ParseFloat(asks.(string), 64)
|
||||
case 1:
|
||||
priceToBeUpdated.Amount, _ = strconv.ParseFloat(asks.(string), 64)
|
||||
}
|
||||
p, err := strconv.ParseFloat(wsdp.UpdateAsks[i][0].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateAsk = append(updateAsk, priceToBeUpdated)
|
||||
a, err := strconv.ParseFloat(wsdp.UpdateAsks[i][1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateAsk = append(updateAsk, orderbook.Item{Price: p, Amount: a})
|
||||
}
|
||||
currencyPair := currency.NewPairFromFormattedPairs(wsdp.Pair, b.GetEnabledPairs(asset.Spot),
|
||||
b.GetPairFormat(asset.Spot, true))
|
||||
|
||||
return b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Bids: updateBid,
|
||||
Asks: updateAsk,
|
||||
CurrencyPair: currencyPair,
|
||||
UpdateID: wsdp.LastUpdateID,
|
||||
AssetType: asset.Spot,
|
||||
Bids: updateBid,
|
||||
Asks: updateAsk,
|
||||
Pair: currencyPair,
|
||||
UpdateID: wsdp.LastUpdateID,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) error {
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -202,7 +202,8 @@ func (b *Binance) Run() {
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies. Err: %s\n",
|
||||
b.Name, err)
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +248,10 @@ func (b *Binance) UpdateTradablePairs(forceUpdate bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate)
|
||||
return b.UpdatePairs(currency.NewPairsFromStrings(pairs),
|
||||
asset.Spot,
|
||||
false,
|
||||
forceUpdate)
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
|
||||
@@ -498,10 +498,10 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books
|
||||
// orderbook sides
|
||||
func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book []WebsocketBook) error {
|
||||
orderbookUpdate := wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: []orderbook.Item{},
|
||||
Bids: []orderbook.Item{},
|
||||
AssetType: assetType,
|
||||
CurrencyPair: p,
|
||||
Asks: []orderbook.Item{},
|
||||
Bids: []orderbook.Item{},
|
||||
Asset: assetType,
|
||||
Pair: p,
|
||||
}
|
||||
|
||||
for i := 0; i < len(book); i++ {
|
||||
@@ -548,6 +548,7 @@ func (b *Bitfinex) GenerateDefaultSubscriptions() {
|
||||
params := make(map[string]interface{})
|
||||
if channels[i] == "book" {
|
||||
params["prec"] = "P0"
|
||||
params["len"] = "100"
|
||||
}
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: channels[i],
|
||||
|
||||
@@ -206,8 +206,17 @@ func (b *Bitmex) wsHandleIncomingData() {
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(orderbooks.Data[0].Symbol)
|
||||
// TODO: update this to support multiple asset types
|
||||
err = b.processOrderbook(orderbooks.Data, orderbooks.Action, p, "CONTRACT")
|
||||
var a asset.Item
|
||||
a, err = b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = b.processOrderbook(orderbooks.Data,
|
||||
orderbooks.Action,
|
||||
p,
|
||||
a)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
@@ -345,12 +354,14 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
|
||||
asks = append(asks, orderbook.Item{
|
||||
Price: data[i].Price,
|
||||
Amount: float64(data[i].Size),
|
||||
ID: data[i].ID,
|
||||
})
|
||||
continue
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Price: data[i].Price,
|
||||
Amount: float64(data[i].Size),
|
||||
ID: data[i].ID,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -379,24 +390,23 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
|
||||
for i := range data {
|
||||
if strings.EqualFold(data[i].Side, "Sell") {
|
||||
asks = append(asks, orderbook.Item{
|
||||
Price: data[i].Price,
|
||||
Amount: float64(data[i].Size),
|
||||
ID: data[i].ID,
|
||||
})
|
||||
continue
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Price: data[i].Price,
|
||||
Amount: float64(data[i].Size),
|
||||
ID: data[i].ID,
|
||||
})
|
||||
}
|
||||
|
||||
err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: currencyPair,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: assetType,
|
||||
Action: action,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: currencyPair,
|
||||
Asset: assetType,
|
||||
Action: action,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -413,7 +423,16 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *Bitmex) GenerateDefaultSubscriptions() {
|
||||
contracts := b.GetEnabledPairs(asset.PerpetualContract)
|
||||
assets := b.GetAssetTypes()
|
||||
var allPairs currency.Pairs
|
||||
|
||||
for x := range assets {
|
||||
contracts := b.GetEnabledPairs(assets[x])
|
||||
for y := range contracts {
|
||||
allPairs = allPairs.Add(contracts[y])
|
||||
}
|
||||
}
|
||||
|
||||
channels := []string{bitmexWSOrderbookL2, bitmexWSTrade}
|
||||
subscriptions := []wshandler.WebsocketChannelSubscription{
|
||||
{
|
||||
@@ -422,10 +441,10 @@ func (b *Bitmex) GenerateDefaultSubscriptions() {
|
||||
}
|
||||
|
||||
for i := range channels {
|
||||
for j := range contracts {
|
||||
for j := range allPairs {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: fmt.Sprintf("%v:%v", channels[i], contracts[j].String()),
|
||||
Currency: contracts[j],
|
||||
Channel: fmt.Sprintf("%v:%v", channels[i], allPairs[j].String()),
|
||||
Currency: allPairs[j],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error {
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ func (b *Bitstamp) WsConnect() error {
|
||||
return err
|
||||
}
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.GetName())
|
||||
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name)
|
||||
}
|
||||
|
||||
err = b.seedOrderBook()
|
||||
@@ -76,7 +76,7 @@ func (b *Bitstamp) WsHandleData() {
|
||||
switch wsResponse.Event {
|
||||
case "bts:request_reconnect":
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket reconnection request received", b.GetName())
|
||||
log.Debugf(log.ExchangeSys, "%v - Websocket reconnection request received", b.Name)
|
||||
}
|
||||
go b.Websocket.Shutdown() // Connection monitor will reconnect
|
||||
|
||||
@@ -89,7 +89,7 @@ func (b *Bitstamp) WsHandleData() {
|
||||
}
|
||||
|
||||
currencyPair := strings.Split(wsResponse.Channel, "_")
|
||||
p := currency.NewPairFromString(strings.ToUpper(currencyPair[3]))
|
||||
p := currency.NewPairFromString(strings.ToUpper(currencyPair[2]))
|
||||
|
||||
err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, asset.Spot)
|
||||
if err != nil {
|
||||
@@ -113,7 +113,7 @@ func (b *Bitstamp) WsHandleData() {
|
||||
Price: wsTradeTemp.Data.Price,
|
||||
Amount: wsTradeTemp.Data.Amount,
|
||||
CurrencyPair: p,
|
||||
Exchange: b.GetName(),
|
||||
Exchange: b.Name,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func (b *Bitstamp) WsHandleData() {
|
||||
}
|
||||
|
||||
func (b *Bitstamp) generateDefaultSubscriptions() {
|
||||
var channels = []string{"live_trades_", "diff_order_book_"}
|
||||
var channels = []string{"live_trades_", "order_book_"}
|
||||
enabledCurrencies := b.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
for i := range channels {
|
||||
@@ -163,47 +163,45 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair,
|
||||
}
|
||||
|
||||
var asks, bids []orderbook.Item
|
||||
if len(update.Asks) > 0 {
|
||||
for i := range update.Asks {
|
||||
target, err := strconv.ParseFloat(update.Asks[i][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(update.Asks[i][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
asks = append(asks, orderbook.Item{Price: target, Amount: amount})
|
||||
for i := range update.Asks {
|
||||
target, err := strconv.ParseFloat(update.Asks[i][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(update.Asks[i][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
asks = append(asks, orderbook.Item{Price: target, Amount: amount})
|
||||
}
|
||||
|
||||
if len(update.Bids) > 0 {
|
||||
for i := range update.Bids {
|
||||
target, err := strconv.ParseFloat(update.Bids[i][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(update.Bids[i][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
bids = append(bids, orderbook.Item{Price: target, Amount: amount})
|
||||
for i := range update.Bids {
|
||||
target, err := strconv.ParseFloat(update.Bids[i][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(update.Bids[i][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
bids = append(bids, orderbook.Item{Price: target, Amount: amount})
|
||||
}
|
||||
err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: p,
|
||||
UpdateID: update.Timestamp,
|
||||
Pair: p,
|
||||
LastUpdated: time.Unix(update.Timestamp, 0),
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: b.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -212,7 +210,7 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair,
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName(),
|
||||
Exchange: b.Name,
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -247,7 +245,7 @@ func (b *Bitstamp) seedOrderBook() error {
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.Pair = p[x]
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.ExchangeName = b.GetName()
|
||||
newOrderBook.ExchangeName = b.Name
|
||||
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
if err != nil {
|
||||
@@ -257,7 +255,7 @@ func (b *Bitstamp) seedOrderBook() error {
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p[x],
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.GetName(),
|
||||
Exchange: b.Name,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -159,13 +159,6 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ func (b *Bittrex) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
|
||||
if s.OrderType != order.Limit {
|
||||
return submitOrderResponse,
|
||||
errors.New("limit order not supported on exchange")
|
||||
errors.New("limit orders only supported on exchange")
|
||||
}
|
||||
|
||||
var response UUID
|
||||
|
||||
@@ -97,11 +97,10 @@ func (c *CoinbasePro) WsHandleData() {
|
||||
Open: ticker.Open24H,
|
||||
High: ticker.High24H,
|
||||
Low: ticker.Low24H,
|
||||
Close: ticker.Price,
|
||||
Last: ticker.Price,
|
||||
Volume: ticker.Volume24H,
|
||||
Bid: ticker.BestBid,
|
||||
Ask: ticker.BestAsk,
|
||||
Last: ticker.LastSize,
|
||||
}
|
||||
|
||||
case "snapshot":
|
||||
@@ -258,11 +257,11 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error {
|
||||
return err
|
||||
}
|
||||
err = c.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: p,
|
||||
UpdateTime: timestamp,
|
||||
AssetType: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: p,
|
||||
UpdateTime: timestamp,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -31,15 +31,22 @@ type Instruments struct {
|
||||
|
||||
// Ticker holds ticker information
|
||||
type Ticker 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 int64 `json:"timestamp"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
Volume24 float64 `json:"volume24,string"`
|
||||
High24 float64 `json:"high24,string"`
|
||||
HighestBuy float64 `json:"highest_buy,string"`
|
||||
InstrumentID int `json:"inst_id"`
|
||||
Last float64 `json:"last,string"`
|
||||
Low24 float64 `json:"low24,string"`
|
||||
LowestSell float64 `json:"lowest_sell,string"`
|
||||
PrevTransID int64 `json:"prev_trans_id"`
|
||||
PriceChange24 float64 `json:"price_change_24,string"`
|
||||
Reply string `json:"reply"`
|
||||
OpenInterest float64 `json:"open_interest,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
Volume24 float64 `json:"volume24,string"`
|
||||
Volume24Quote float64 `json:"volume24_quote,string"`
|
||||
VolumeQuote float64 `json:"volume_quote,string"`
|
||||
}
|
||||
|
||||
// OrderbookBase is a sub-type holding price and quantity
|
||||
|
||||
@@ -136,13 +136,16 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
c.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
currencyPair := wsInstrumentMap.LookupInstrument(ticker.InstID)
|
||||
c.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Exchange: c.Name,
|
||||
Volume: ticker.Volume,
|
||||
QuoteVolume: ticker.VolumeQuote,
|
||||
High: ticker.HighestBuy,
|
||||
Low: ticker.LowestSell,
|
||||
Volume: ticker.Volume24,
|
||||
QuoteVolume: ticker.Volume24Quote,
|
||||
Bid: ticker.HighestBuy,
|
||||
Ask: ticker.LowestSell,
|
||||
High: ticker.High24,
|
||||
Low: ticker.Low24,
|
||||
Last: ticker.Last,
|
||||
Timestamp: time.Unix(0, ticker.Timestamp),
|
||||
AssetType: asset.Spot,
|
||||
@@ -302,9 +305,9 @@ func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error {
|
||||
c.GetPairFormat(asset.Spot, true),
|
||||
)
|
||||
bufferUpdate := &wsorderbook.WebsocketOrderbookUpdate{
|
||||
CurrencyPair: p,
|
||||
UpdateID: update.TransID,
|
||||
AssetType: asset.Spot,
|
||||
Pair: p,
|
||||
UpdateID: update.TransID,
|
||||
Asset: asset.Spot,
|
||||
}
|
||||
if strings.EqualFold(update.Side, order.Buy.Lower()) {
|
||||
bufferUpdate.Bids = []orderbook.Item{{Price: update.Price, Amount: update.Volume}}
|
||||
|
||||
@@ -342,8 +342,10 @@ func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri
|
||||
}
|
||||
tickerPrice = ticker.Price{
|
||||
Last: tick.Last,
|
||||
High: tick.HighestBuy,
|
||||
Low: tick.LowestSell,
|
||||
High: tick.High24,
|
||||
Low: tick.Low24,
|
||||
Bid: tick.HighestBuy,
|
||||
Ask: tick.LowestSell,
|
||||
Volume: tick.Volume24,
|
||||
Pair: p,
|
||||
LastUpdated: time.Unix(0, tick.Timestamp),
|
||||
|
||||
@@ -230,6 +230,16 @@ func (e *Base) GetAssetTypes() asset.Items {
|
||||
return e.CurrencyPairs.AssetTypes
|
||||
}
|
||||
|
||||
// GetPairAssetType returns the associated asset type for the currency pair
|
||||
func (e *Base) GetPairAssetType(c currency.Pair) (asset.Item, error) {
|
||||
for i := range e.GetAssetTypes() {
|
||||
if e.GetEnabledPairs(e.GetAssetTypes()[i]).Contains(c, true) {
|
||||
return e.GetAssetTypes()[i], nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("asset type not associated with currency pair")
|
||||
}
|
||||
|
||||
// GetClientBankAccounts returns banking details associated with
|
||||
// a client for withdrawal purposes
|
||||
func (e *Base) GetClientBankAccounts(exchangeName, withdrawalCurrency string) (config.BankAccount, error) {
|
||||
|
||||
@@ -1357,3 +1357,29 @@ func TestGetBase(t *testing.T) {
|
||||
t.Error("name should be rawr")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAssetType(t *testing.T) {
|
||||
var b Base
|
||||
p := currency.NewPair(currency.BTC, currency.USD)
|
||||
_, err := b.GetPairAssetType(p)
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
b.CurrencyPairs.AssetTypes = asset.Items{asset.Spot}
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Enabled: currency.Pairs{
|
||||
currency.NewPair(currency.BTC, currency.USD),
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
}
|
||||
|
||||
a, err := b.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if a != asset.Spot {
|
||||
t.Error("should be spot but is", a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,11 +246,11 @@ func (g *Gateio) WsHandleData() {
|
||||
} else {
|
||||
err = g.Websocket.Orderbook.Update(
|
||||
&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
CurrencyPair: currency.NewPairFromString(c),
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Pair: currency.NewPairFromString(c),
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
|
||||
@@ -319,11 +319,11 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa
|
||||
}
|
||||
}
|
||||
err := g.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
CurrencyPair: pair,
|
||||
UpdateTime: time.Unix(0, result.TimestampMS),
|
||||
AssetType: asset.Spot,
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Pair: pair,
|
||||
UpdateTime: time.Unix(0, result.TimestampMS),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v %v", g.Name, err)
|
||||
|
||||
@@ -284,11 +284,11 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error {
|
||||
p := currency.NewPairFromFormattedPairs(update.Params.Symbol,
|
||||
h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true))
|
||||
err := h.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
CurrencyPair: p,
|
||||
UpdateID: update.Params.Sequence,
|
||||
AssetType: asset.Spot,
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Pair: p,
|
||||
UpdateID: update.Params.Sequence,
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -320,10 +320,10 @@ type WsDepth struct {
|
||||
Channel string `json:"ch"`
|
||||
Timestamp int64 `json:"ts"`
|
||||
Tick struct {
|
||||
Bids []interface{} `json:"bids"`
|
||||
Asks []interface{} `json:"asks"`
|
||||
Timestamp int64 `json:"ts"`
|
||||
Version int64 `json:"version"`
|
||||
Bids [][]interface{} `json:"bids"`
|
||||
Asks [][]interface{} `json:"asks"`
|
||||
Timestamp int64 `json:"ts"`
|
||||
Version int64 `json:"version"`
|
||||
} `json:"tick"`
|
||||
}
|
||||
|
||||
|
||||
@@ -233,8 +233,14 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
data := strings.Split(depth.Channel, ".")
|
||||
h.WsProcessOrderbook(&depth, data[1])
|
||||
err = h.WsProcessOrderbook(&depth, data[1])
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
case strings.Contains(init.Channel, "kline"):
|
||||
var kline WsKline
|
||||
err := common.JSONDecode(resp.Raw, &kline)
|
||||
@@ -297,32 +303,41 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
// WsProcessOrderbook processes new orderbook data
|
||||
func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error {
|
||||
p := currency.NewPairFromFormattedPairs(symbol,
|
||||
h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true))
|
||||
h.GetEnabledPairs(asset.Spot),
|
||||
h.GetPairFormat(asset.Spot, true))
|
||||
|
||||
var bids, asks []orderbook.Item
|
||||
for i := 0; i < len(update.Tick.Bids); i++ {
|
||||
bidLevel := update.Tick.Bids[i].([]interface{})
|
||||
bids = append(bids, orderbook.Item{Price: bidLevel[0].(float64),
|
||||
Amount: bidLevel[0].(float64)})
|
||||
for i := range update.Tick.Bids {
|
||||
bids = append(bids, orderbook.Item{
|
||||
Price: update.Tick.Bids[i][0].(float64),
|
||||
Amount: update.Tick.Bids[i][1].(float64),
|
||||
})
|
||||
}
|
||||
for i := 0; i < len(update.Tick.Asks); i++ {
|
||||
askLevel := update.Tick.Asks[i].([]interface{})
|
||||
asks = append(asks, orderbook.Item{Price: askLevel[0].(float64),
|
||||
Amount: askLevel[0].(float64)})
|
||||
|
||||
for i := range update.Tick.Asks {
|
||||
asks = append(asks, orderbook.Item{
|
||||
Price: update.Tick.Asks[i][0].(float64),
|
||||
Amount: update.Tick.Asks[i][1].(float64),
|
||||
})
|
||||
}
|
||||
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.Pair = p
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.ExchangeName = h.Name
|
||||
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Exchange: h.GetName(),
|
||||
Asset: asset.Spot,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -343,7 +343,7 @@ func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderboo
|
||||
var orderBook orderbook.Base
|
||||
orderbookNew, err := h.GetDepth(OrderBookDataRequestParams{
|
||||
Symbol: h.FormatExchangeCurrency(p, assetType).String(),
|
||||
Type: OrderBookDataRequestParamsTypeStep1,
|
||||
Type: OrderBookDataRequestParamsTypeStep0,
|
||||
})
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
|
||||
@@ -143,31 +143,31 @@ func (k *Kraken) WsHandleDataResponse(response WebsocketDataResponse) {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket ticker data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessTickers(&channelData, response[1])
|
||||
k.wsProcessTickers(&channelData, response[1].(map[string]interface{}))
|
||||
case krakenWsOHLC:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket OHLC data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessCandles(&channelData, response[1])
|
||||
k.wsProcessCandles(&channelData, response[1].([]interface{}))
|
||||
case krakenWsOrderbook:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket Orderbook data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessOrderBook(&channelData, response[1])
|
||||
k.wsProcessOrderBook(&channelData, response[1].(map[string]interface{}))
|
||||
case krakenWsSpread:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket Spread data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessSpread(&channelData, response[1])
|
||||
k.wsProcessSpread(&channelData, response[1].([]interface{}))
|
||||
case krakenWsTrade:
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Websocket Trade data received",
|
||||
k.Name)
|
||||
}
|
||||
k.wsProcessTrades(&channelData, response[1])
|
||||
k.wsProcessTrades(&channelData, response[1].([]interface{}))
|
||||
default:
|
||||
log.Errorf(log.ExchangeSys, "%v Unidentified websocket data received: %v",
|
||||
k.Name,
|
||||
@@ -238,22 +238,48 @@ func getSubscriptionChannelData(id int64) WebsocketChannelData {
|
||||
}
|
||||
|
||||
// wsProcessTickers converts ticker data and sends it to the datahandler
|
||||
func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data interface{}) {
|
||||
tickerData := data.(map[string]interface{})
|
||||
askData := tickerData["a"].([]interface{})
|
||||
bidData := tickerData["b"].([]interface{})
|
||||
closeData := tickerData["c"].([]interface{})
|
||||
openData := tickerData["o"].([]interface{})
|
||||
lowData := tickerData["l"].([]interface{})
|
||||
highData := tickerData["h"].([]interface{})
|
||||
volumeData := tickerData["v"].([]interface{})
|
||||
closePrice, _ := strconv.ParseFloat(closeData[0].(string), 64)
|
||||
openPrice, _ := strconv.ParseFloat(openData[0].(string), 64)
|
||||
highPrice, _ := strconv.ParseFloat(highData[0].(string), 64)
|
||||
lowPrice, _ := strconv.ParseFloat(lowData[0].(string), 64)
|
||||
quantity, _ := strconv.ParseFloat(volumeData[0].(string), 64)
|
||||
ask, _ := strconv.ParseFloat(askData[0].(string), 64)
|
||||
bid, _ := strconv.ParseFloat(bidData[0].(string), 64)
|
||||
func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data map[string]interface{}) {
|
||||
closePrice, err := strconv.ParseFloat(data["c"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
openPrice, err := strconv.ParseFloat(data["o"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
highPrice, err := strconv.ParseFloat(data["h"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
lowPrice, err := strconv.ParseFloat(data["l"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
quantity, err := strconv.ParseFloat(data["v"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
ask, err := strconv.ParseFloat(data["a"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
bid, err := strconv.ParseFloat(data["b"].([]interface{})[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Exchange: k.Name,
|
||||
@@ -271,13 +297,17 @@ func (k *Kraken) wsProcessTickers(channelData *WebsocketChannelData, data interf
|
||||
}
|
||||
|
||||
// wsProcessTickers converts ticker data and sends it to the datahandler
|
||||
func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data interface{}) {
|
||||
spreadData := data.([]interface{})
|
||||
bestBid := spreadData[0].(string)
|
||||
bestAsk := spreadData[1].(string)
|
||||
timeData, _ := strconv.ParseFloat(spreadData[2].(string), 64)
|
||||
bidVolume := spreadData[3].(string)
|
||||
askVolume := spreadData[4].(string)
|
||||
func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data []interface{}) {
|
||||
bestBid := data[0].(string)
|
||||
bestAsk := data[1].(string)
|
||||
timeData, err := strconv.ParseFloat(data[2].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
bidVolume := data[3].(string)
|
||||
askVolume := data[4].(string)
|
||||
sec, dec := math.Modf(timeData)
|
||||
spreadTimestamp := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
if k.Verbose {
|
||||
@@ -294,14 +324,28 @@ func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data interfa
|
||||
}
|
||||
|
||||
// wsProcessTrades converts trade data and sends it to the datahandler
|
||||
func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data interface{}) {
|
||||
tradeData := data.([]interface{})
|
||||
for i := range tradeData {
|
||||
trade := tradeData[i].([]interface{})
|
||||
timeData, _ := strconv.ParseInt(trade[2].(string), 10, 64)
|
||||
timeUnix := time.Unix(timeData, 0)
|
||||
price, _ := strconv.ParseFloat(trade[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(trade[1].(string), 64)
|
||||
func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []interface{}) {
|
||||
for i := range data {
|
||||
trade := data[i].([]interface{})
|
||||
timeData, err := strconv.ParseFloat(trade[2].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
sec, dec := math.Modf(timeData)
|
||||
timeUnix := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
|
||||
price, err := strconv.ParseFloat(trade[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(trade[1].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- wshandler.TradeData{
|
||||
AssetType: asset.Spot,
|
||||
@@ -318,17 +362,17 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data interfa
|
||||
|
||||
// wsProcessOrderBook determines if the orderbook data is partial or update
|
||||
// Then sends to appropriate fun
|
||||
func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data interface{}) {
|
||||
obData := data.(map[string]interface{})
|
||||
if _, ok := obData["as"]; ok {
|
||||
k.wsProcessOrderBookPartial(channelData, obData)
|
||||
func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[string]interface{}) {
|
||||
if fullAsk, ok := data["as"].([]interface{}); ok {
|
||||
fullBids := data["as"].([]interface{})
|
||||
k.wsProcessOrderBookPartial(channelData, fullAsk, fullBids)
|
||||
} else {
|
||||
_, asksExist := obData["a"]
|
||||
_, bidsExist := obData["b"]
|
||||
askData, asksExist := data["a"].([]interface{})
|
||||
bidData, bidsExist := data["b"].([]interface{})
|
||||
if asksExist || bidsExist {
|
||||
k.wsRequestMtx.Lock()
|
||||
defer k.wsRequestMtx.Unlock()
|
||||
err := k.wsProcessOrderBookUpdate(channelData, obData)
|
||||
err := k.wsProcessOrderBookUpdate(channelData, askData, bidData)
|
||||
if err != nil {
|
||||
subscriptionToRemove := wshandler.WebsocketChannelSubscription{
|
||||
Channel: krakenWsOrderbook,
|
||||
@@ -341,40 +385,64 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
|
||||
}
|
||||
|
||||
// wsProcessOrderBookPartial creates a new orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, obData map[string]interface{}) {
|
||||
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, askData, bidData []interface{}) {
|
||||
base := orderbook.Base{
|
||||
Pair: channelData.Pair,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
// Kraken ob data is timestamped per price, GCT orderbook data is timestamped per entry
|
||||
// Using the highest last update time, we can attempt to respect both within a reasonable degree
|
||||
// Kraken ob data is timestamped per price, GCT orderbook data is
|
||||
// timestamped per entry using the highest last update time, we can attempt
|
||||
// to respect both within a reasonable degree
|
||||
var highestLastUpdate time.Time
|
||||
askData := obData["as"].([]interface{})
|
||||
for i := range askData {
|
||||
asks := askData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(asks[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(asks[1].(string), 64)
|
||||
price, err := strconv.ParseFloat(asks[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
amount, err := strconv.ParseFloat(asks[1].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
base.Asks = append(base.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
timeData, _ := strconv.ParseFloat(asks[2].(string), 64)
|
||||
timeData, err := strconv.ParseFloat(asks[2].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
sec, dec := math.Modf(timeData)
|
||||
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
if highestLastUpdate.Before(askUpdatedTime) {
|
||||
highestLastUpdate = askUpdatedTime
|
||||
}
|
||||
}
|
||||
bidData := obData["bs"].([]interface{})
|
||||
|
||||
for i := range bidData {
|
||||
bids := bidData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(bids[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(bids[1].(string), 64)
|
||||
price, err := strconv.ParseFloat(bids[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
amount, err := strconv.ParseFloat(bids[1].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
base.Bids = append(base.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
timeData, _ := strconv.ParseFloat(bids[2].(string), 64)
|
||||
timeData, err := strconv.ParseFloat(bids[2].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
sec, dec := math.Modf(timeData)
|
||||
bidUpdateTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
if highestLastUpdate.Before(bidUpdateTime) {
|
||||
@@ -382,6 +450,7 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
|
||||
}
|
||||
}
|
||||
base.LastUpdated = highestLastUpdate
|
||||
base.ExchangeName = k.Name
|
||||
err := k.Websocket.Orderbook.LoadSnapshot(&base)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
@@ -395,48 +464,68 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
|
||||
}
|
||||
|
||||
// wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, obData map[string]interface{}) error {
|
||||
func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, askData, bidData []interface{}) error {
|
||||
update := wsorderbook.WebsocketOrderbookUpdate{
|
||||
AssetType: asset.Spot,
|
||||
CurrencyPair: channelData.Pair,
|
||||
Asset: asset.Spot,
|
||||
Pair: channelData.Pair,
|
||||
}
|
||||
|
||||
var highestLastUpdate time.Time
|
||||
// Ask data is not always sent
|
||||
if _, ok := obData["a"]; ok {
|
||||
askData := obData["a"].([]interface{})
|
||||
for i := range askData {
|
||||
asks := askData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(asks[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(asks[1].(string), 64)
|
||||
update.Asks = append(update.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
timeData, _ := strconv.ParseFloat(asks[2].(string), 64)
|
||||
sec, dec := math.Modf(timeData)
|
||||
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
if highestLastUpdate.Before(askUpdatedTime) {
|
||||
highestLastUpdate = askUpdatedTime
|
||||
}
|
||||
for i := range askData {
|
||||
asks := askData[i].([]interface{})
|
||||
price, err := strconv.ParseFloat(asks[0].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(asks[1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
update.Asks = append(update.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
timeData, err := strconv.ParseFloat(asks[2].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sec, dec := math.Modf(timeData)
|
||||
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
if highestLastUpdate.Before(askUpdatedTime) {
|
||||
highestLastUpdate = askUpdatedTime
|
||||
}
|
||||
}
|
||||
|
||||
// Bid data is not always sent
|
||||
if _, ok := obData["b"]; ok {
|
||||
bidData := obData["b"].([]interface{})
|
||||
for i := range bidData {
|
||||
bids := bidData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(bids[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(bids[1].(string), 64)
|
||||
update.Bids = append(update.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
timeData, _ := strconv.ParseFloat(bids[2].(string), 64)
|
||||
sec, dec := math.Modf(timeData)
|
||||
bidUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
if highestLastUpdate.Before(bidUpdatedTime) {
|
||||
highestLastUpdate = bidUpdatedTime
|
||||
}
|
||||
for i := range bidData {
|
||||
bids := bidData[i].([]interface{})
|
||||
price, err := strconv.ParseFloat(bids[0].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(bids[1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
update.Bids = append(update.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
timeData, err := strconv.ParseFloat(bids[2].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sec, dec := math.Modf(timeData)
|
||||
bidUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
if highestLastUpdate.Before(bidUpdatedTime) {
|
||||
highestLastUpdate = bidUpdatedTime
|
||||
}
|
||||
}
|
||||
update.UpdateTime = highestLastUpdate
|
||||
@@ -454,17 +543,52 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, obD
|
||||
}
|
||||
|
||||
// wsProcessCandles converts candle data and sends it to the data handler
|
||||
func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interface{}) {
|
||||
candleData := data.([]interface{})
|
||||
startTimeData, _ := strconv.ParseInt(candleData[0].(string), 10, 64)
|
||||
startTimeUnix := time.Unix(startTimeData, 0)
|
||||
endTimeData, _ := strconv.ParseInt(candleData[1].(string), 10, 64)
|
||||
endTimeUnix := time.Unix(endTimeData, 0)
|
||||
openPrice, _ := strconv.ParseFloat(candleData[2].(string), 64)
|
||||
highPrice, _ := strconv.ParseFloat(candleData[3].(string), 64)
|
||||
lowPrice, _ := strconv.ParseFloat(candleData[4].(string), 64)
|
||||
closePrice, _ := strconv.ParseFloat(candleData[5].(string), 64)
|
||||
volume, _ := strconv.ParseFloat(candleData[7].(string), 64)
|
||||
func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []interface{}) {
|
||||
startTime, err := strconv.ParseFloat(data[0].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
sec, dec := math.Modf(startTime)
|
||||
startTimeUnix := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
|
||||
endTime, err := strconv.ParseFloat(data[1].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
sec, dec = math.Modf(endTime)
|
||||
endTimeUnix := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
|
||||
openPrice, err := strconv.ParseFloat(data[2].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
highPrice, err := strconv.ParseFloat(data[3].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
lowPrice, err := strconv.ParseFloat(data[4].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
closePrice, err := strconv.ParseFloat(data[5].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
volume, err := strconv.ParseFloat(data[7].(string), 64)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- wshandler.KlineData{
|
||||
AssetType: asset.Spot,
|
||||
@@ -501,11 +625,17 @@ func (k *Kraken) GenerateDefaultSubscriptions() {
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (k *Kraken) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
var depth int64
|
||||
if channelToSubscribe.Channel == "book" {
|
||||
depth = 1000
|
||||
}
|
||||
|
||||
resp := WebsocketSubscriptionEventRequest{
|
||||
Event: krakenWsSubscribe,
|
||||
Pairs: []string{channelToSubscribe.Currency.String()},
|
||||
Subscription: WebsocketSubscriptionData{
|
||||
Name: channelToSubscribe.Channel,
|
||||
Name: channelToSubscribe.Channel,
|
||||
Depth: depth,
|
||||
},
|
||||
RequestID: k.WebsocketConn.GenerateMessageID(false),
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func (k *Kraken) SetDefaults() {
|
||||
k.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
REST: true,
|
||||
Websocket: false,
|
||||
Websocket: true,
|
||||
RESTCapabilities: protocol.Features{
|
||||
TickerBatching: true,
|
||||
TickerFetching: true,
|
||||
|
||||
@@ -45,7 +45,8 @@ func (l *LakeBTC) GetTicker() (map[string]Ticker, error) {
|
||||
response := make(map[string]TickerResponse)
|
||||
path := fmt.Sprintf("%s/%s", l.API.Endpoints.URL, lakeBTCTicker)
|
||||
|
||||
if err := l.SendHTTPRequest(path, &response); err != nil {
|
||||
err := l.SendHTTPRequest(path, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -55,22 +56,40 @@ func (l *LakeBTC) GetTicker() (map[string]Ticker, error) {
|
||||
var tick Ticker
|
||||
key := strings.ToUpper(k)
|
||||
if v.Ask != nil {
|
||||
tick.Ask, _ = strconv.ParseFloat(v.Ask.(string), 64)
|
||||
tick.Ask, err = strconv.ParseFloat(v.Ask.(string), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if v.Bid != nil {
|
||||
tick.Bid, _ = strconv.ParseFloat(v.Bid.(string), 64)
|
||||
tick.Bid, err = strconv.ParseFloat(v.Bid.(string), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if v.High != nil {
|
||||
tick.High, _ = strconv.ParseFloat(v.High.(string), 64)
|
||||
tick.High, err = strconv.ParseFloat(v.High.(string), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if v.Last != nil {
|
||||
tick.Last, _ = strconv.ParseFloat(v.Last.(string), 64)
|
||||
tick.Last, err = strconv.ParseFloat(v.Last.(string), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if v.Low != nil {
|
||||
tick.Low, _ = strconv.ParseFloat(v.Low.(string), 64)
|
||||
tick.Low, err = strconv.ParseFloat(v.Low.(string), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if v.Volume != nil {
|
||||
tick.Volume, _ = strconv.ParseFloat(v.Volume.(string), 64)
|
||||
tick.Volume, err = strconv.ParseFloat(v.Volume.(string), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
result[key] = tick
|
||||
}
|
||||
|
||||
@@ -74,9 +74,14 @@ func (l *LakeBTC) listenToEndpoints() error {
|
||||
func (l *LakeBTC) GenerateDefaultSubscriptions() {
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
enabledCurrencies := l.GetEnabledPairs(asset.Spot)
|
||||
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = ""
|
||||
channel := fmt.Sprintf("%v%v%v", marketSubstring, enabledCurrencies[j].Lower(), globalSubstring)
|
||||
channel := fmt.Sprintf("%v%v%v",
|
||||
marketSubstring,
|
||||
enabledCurrencies[j].Lower(),
|
||||
globalSubstring)
|
||||
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: channel,
|
||||
Currency: enabledCurrencies[j],
|
||||
@@ -164,8 +169,11 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := l.getCurrencyFromChannel(channel)
|
||||
|
||||
book := orderbook.Base{
|
||||
Pair: l.getCurrencyFromChannel(channel),
|
||||
Pair: p,
|
||||
LastUpdated: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: l.Name,
|
||||
@@ -188,6 +196,7 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error {
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
|
||||
for i := 0; i < len(update.Bids); i++ {
|
||||
var amount, price float64
|
||||
amount, err = strconv.ParseFloat(update.Bids[i][1], 64)
|
||||
@@ -205,7 +214,19 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error {
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
return l.Websocket.Orderbook.LoadSnapshot(&book)
|
||||
|
||||
err = l.Websocket.Orderbook.LoadSnapshot(&book)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: asset.Spot,
|
||||
Exchange: l.Name,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LakeBTC) getCurrencyFromChannel(channel string) currency.Pair {
|
||||
@@ -221,7 +242,14 @@ func (l *LakeBTC) processTicker(ticker string) error {
|
||||
l.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
|
||||
enabled := l.GetEnabledPairs(asset.Spot)
|
||||
for k, v := range tUpdate {
|
||||
returnCurrency := currency.NewPairFromString(k)
|
||||
if !enabled.Contains(returnCurrency, true) {
|
||||
continue
|
||||
}
|
||||
|
||||
tickerData := v.(map[string]interface{})
|
||||
processTickerItem := func(tick map[string]interface{}, item string) float64 {
|
||||
if tick[item] == nil {
|
||||
@@ -230,7 +258,10 @@ func (l *LakeBTC) processTicker(ticker string) error {
|
||||
|
||||
p, err := strconv.ParseFloat(tick[item].(string), 64)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%s error parsing ticker data '%s' %v", l.Name, item, tickerData)
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%s error parsing ticker data '%s' %v",
|
||||
l.Name,
|
||||
item,
|
||||
tickerData)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -246,7 +277,7 @@ func (l *LakeBTC) processTicker(ticker string) error {
|
||||
Ask: processTickerItem(tickerData, tickerSellString),
|
||||
Volume: processTickerItem(tickerData, tickerVolumeString),
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromString(k),
|
||||
Pair: returnCurrency,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -214,20 +214,21 @@ func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr
|
||||
|
||||
pairs := l.GetEnabledPairs(assetType)
|
||||
for i := range pairs {
|
||||
currency := l.FormatExchangeCurrency(pairs[i], assetType).String()
|
||||
if _, ok := ticks[currency]; !ok {
|
||||
c, ok := ticks[l.FormatExchangeCurrency(pairs[i], assetType).String()]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var tickerPrice ticker.Price
|
||||
tickerPrice.Pair = pairs[i]
|
||||
tickerPrice.Ask = ticks[currency].Ask
|
||||
tickerPrice.Bid = ticks[currency].Bid
|
||||
tickerPrice.Volume = ticks[currency].Volume
|
||||
tickerPrice.High = ticks[currency].High
|
||||
tickerPrice.Low = ticks[currency].Low
|
||||
tickerPrice.Last = ticks[currency].Last
|
||||
tickerPrice.Ask = c.Ask
|
||||
tickerPrice.Bid = c.Bid
|
||||
tickerPrice.Volume = c.Volume
|
||||
tickerPrice.High = c.High
|
||||
tickerPrice.Low = c.Low
|
||||
tickerPrice.Last = c.Last
|
||||
|
||||
err = ticker.ProcessTicker(l.GetName(), &tickerPrice, assetType)
|
||||
err = ticker.ProcessTicker(l.Name, &tickerPrice, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
@@ -237,7 +238,7 @@ func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr
|
||||
|
||||
// FetchTicker returns the ticker for a currency pair
|
||||
func (l *LakeBTC) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Price, error) {
|
||||
tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType)
|
||||
tickerNew, err := ticker.GetTicker(l.Name, p, assetType)
|
||||
if err != nil {
|
||||
return l.UpdateTicker(p, assetType)
|
||||
}
|
||||
@@ -246,7 +247,7 @@ func (l *LakeBTC) FetchTicker(p currency.Pair, assetType asset.Item) (ticker.Pri
|
||||
|
||||
// FetchOrderbook returns orderbook base on the currency pair
|
||||
func (l *LakeBTC) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) {
|
||||
ob, err := orderbook.Get(l.GetName(), p, assetType)
|
||||
ob, err := orderbook.Get(l.Name, p, assetType)
|
||||
if err != nil {
|
||||
return l.UpdateOrderbook(p, assetType)
|
||||
}
|
||||
@@ -270,7 +271,7 @@ func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb
|
||||
}
|
||||
|
||||
orderBook.Pair = p
|
||||
orderBook.ExchangeName = l.GetName()
|
||||
orderBook.ExchangeName = l.Name
|
||||
orderBook.AssetType = assetType
|
||||
|
||||
err = orderBook.Process()
|
||||
@@ -285,7 +286,7 @@ func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderb
|
||||
// LakeBTC exchange
|
||||
func (l *LakeBTC) GetAccountInfo() (exchange.AccountInfo, error) {
|
||||
var response exchange.AccountInfo
|
||||
response.Exchange = l.GetName()
|
||||
response.Exchange = l.Name
|
||||
accountInfo, err := l.GetAccountInformation()
|
||||
if err != nil {
|
||||
return response, err
|
||||
|
||||
@@ -488,18 +488,6 @@ func TestGetSpotTokenPairDetails(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSpotOrderBook API endpoint test
|
||||
func TestGetSpotOrderBook(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
request := okgroup.GetSpotOrderBookRequest{
|
||||
InstrumentID: spotCurrency,
|
||||
}
|
||||
_, err := o.GetSpotOrderBook(request)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSpotAllTokenPairsInformation API endpoint test
|
||||
func TestGetSpotAllTokenPairsInformation(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
|
||||
@@ -142,7 +142,11 @@ func (o *OKCoin) Start(wg *sync.WaitGroup) {
|
||||
// Run implements the OKEX wrapper
|
||||
func (o *OKCoin) Run() {
|
||||
if o.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL)
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Websocket: %s. (url: %s).\n",
|
||||
o.Name,
|
||||
common.IsEnabled(o.Websocket.IsEnabled()),
|
||||
o.WebsocketURL)
|
||||
}
|
||||
|
||||
forceUpdate := false
|
||||
@@ -161,7 +165,9 @@ func (o *OKCoin) Run() {
|
||||
|
||||
err := o.UpdatePairs(enabledPairs, asset.Spot, true, true)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", o.GetName())
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies.\n",
|
||||
o.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -172,7 +178,10 @@ func (o *OKCoin) Run() {
|
||||
|
||||
err := o.UpdateTradablePairs(forceUpdate)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", o.Name, err)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update tradable pairs. Err: %s",
|
||||
o.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,12 +246,12 @@ func (o *OKCoin) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pri
|
||||
}
|
||||
}
|
||||
}
|
||||
return ticker.GetTicker(o.GetName(), p, assetType)
|
||||
return ticker.GetTicker(o.Name, p, assetType)
|
||||
}
|
||||
|
||||
// FetchTicker returns the ticker for a currency pair
|
||||
func (o *OKCoin) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) {
|
||||
tickerData, err = ticker.GetTicker(o.GetName(), p, assetType)
|
||||
tickerData, err = ticker.GetTicker(o.Name, p, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateTicker(p, assetType)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package okex
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
@@ -18,7 +16,7 @@ const (
|
||||
okExAPIVersion = "/v3/"
|
||||
okExExchangeName = "OKEX"
|
||||
// OkExWebsocketURL WebsocketURL
|
||||
OkExWebsocketURL = "wss://real.okex.com:10442/ws/v3"
|
||||
OkExWebsocketURL = "wss://real.okex.com:8443/ws/v3"
|
||||
// API subsections
|
||||
okGroupFuturesSubsection = "futures"
|
||||
okGroupSwapSubsection = "swap"
|
||||
@@ -141,69 +139,6 @@ func (o *OKEX) GetFuturesContractInformation() (resp []okgroup.GetFuturesContrac
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, okgroup.OKGroupInstruments, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetFuturesOrderBook List all contracts. This request does not support pagination. The full list will be returned for a request.
|
||||
func (o *OKEX) GetFuturesOrderBook(request okgroup.GetFuturesOrderBookRequest) (resp okgroup.GetFuturesOrderBookResponse, err error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okgroup.OKGroupGetSpotOrderBook, okgroup.FormatParameters(request))
|
||||
|
||||
type tempOB struct {
|
||||
Bids [][]string `json:"bids"`
|
||||
Asks [][]string `json:"asks"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
var tmpOB tempOB
|
||||
err = o.SendHTTPRequest(http.MethodGet, okGroupFuturesSubsection, requestURL, nil, &tmpOB, false)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
processOB := func(ob [][]string) ([]okgroup.FuturesOrderbookItem, error) {
|
||||
var processedOB []okgroup.FuturesOrderbookItem
|
||||
for x := range ob {
|
||||
price, convErr := strconv.ParseFloat(ob[x][0], 64)
|
||||
if err != nil {
|
||||
return nil, convErr
|
||||
}
|
||||
|
||||
size, convErr := strconv.ParseInt(ob[x][1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, convErr
|
||||
}
|
||||
|
||||
liqOrders, convErr := strconv.ParseInt(ob[x][2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, convErr
|
||||
}
|
||||
|
||||
numOrders, convErr := strconv.ParseInt(ob[x][3], 10, 64)
|
||||
if err != nil {
|
||||
return nil, convErr
|
||||
}
|
||||
|
||||
processedOB = append(processedOB, okgroup.FuturesOrderbookItem{
|
||||
Price: price,
|
||||
Size: size,
|
||||
ForceLiquidatedOrders: liqOrders,
|
||||
NumberOrders: numOrders,
|
||||
})
|
||||
}
|
||||
return processedOB, nil
|
||||
}
|
||||
|
||||
resp.Bids, err = processOB(tmpOB.Bids)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp.Asks, err = processOB(tmpOB.Asks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp.Timestamp = tmpOB.Timestamp
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetAllFuturesTokenInfo Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all contracts.
|
||||
func (o *OKEX) GetAllFuturesTokenInfo() (resp []okgroup.GetFuturesTokenInfoResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupInstruments, okgroup.OKGroupTicker)
|
||||
@@ -373,12 +308,6 @@ func (o *OKEX) GetSwapContractInformation() (resp []okgroup.GetSwapContractInfor
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, okgroup.OKGroupInstruments, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSwapOrderBook Get the charts of the trading pairs.
|
||||
func (o *OKEX) GetSwapOrderBook(request okgroup.GetSwapOrderBookRequest) (resp okgroup.GetSwapOrderBookResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", okgroup.OKGroupInstruments, request.InstrumentID, okGroupDepth, okgroup.FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupSwapSubsection, requestURL, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetAllSwapTokensInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all contracts.
|
||||
func (o *OKEX) GetAllSwapTokensInformation() (resp []okgroup.GetAllSwapTokensInformationResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", okgroup.OKGroupInstruments, okgroup.OKGroupTicker)
|
||||
|
||||
@@ -522,19 +522,6 @@ func TestGetSpotTokenPairDetails(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSpotOrderBook API endpoint test
|
||||
func TestGetSpotOrderBook(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
t.Parallel()
|
||||
request := okgroup.GetSpotOrderBookRequest{
|
||||
InstrumentID: spotCurrency,
|
||||
}
|
||||
_, err := o.GetSpotOrderBook(request)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSpotAllTokenPairsInformation API endpoint test
|
||||
func TestGetSpotAllTokenPairsInformation(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
@@ -1035,18 +1022,6 @@ func TestGetFuturesContractInformation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetFuturesContractInformation API endpoint test
|
||||
func TestGetFuturesOrderBook(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
_, err := o.GetFuturesOrderBook(okgroup.GetFuturesOrderBookRequest{
|
||||
InstrumentID: getFutureInstrumentID(),
|
||||
Size: 10,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetAllFuturesTokenInfo API endpoint test
|
||||
func TestGetAllFuturesTokenInfo(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
@@ -1315,19 +1290,6 @@ func TestGetSwapContractInformation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSwapOrderBook API endpoint test
|
||||
func TestGetSwapOrderBook(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
t.Parallel()
|
||||
_, err := o.GetSwapOrderBook(okgroup.GetSwapOrderBookRequest{
|
||||
InstrumentID: fmt.Sprintf("%v-%v-SWAP", currency.BTC, currency.USD),
|
||||
Size: 200,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetAllSwapTokensInformation API endpoint test
|
||||
func TestGetAllSwapTokensInformation(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package okex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -17,6 +19,11 @@ import (
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
delimiterDash = "-"
|
||||
delimiterUnderscore = "_"
|
||||
)
|
||||
|
||||
// GetDefaultConfig returns a default exchange config
|
||||
func (o *OKEX) GetDefaultConfig() (*config.ExchangeConfig, error) {
|
||||
o.SetDefaults()
|
||||
@@ -64,28 +71,38 @@ func (o *OKEX) SetDefaults() {
|
||||
fmt1 := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
Delimiter: delimiterDash,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "_",
|
||||
Delimiter: delimiterUnderscore,
|
||||
},
|
||||
}
|
||||
o.CurrencyPairs.Store(asset.PerpetualSwap, fmt1)
|
||||
o.CurrencyPairs.Store(asset.Futures, fmt1)
|
||||
|
||||
fmt2 := currency.PairStore{
|
||||
index := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
Delimiter: delimiterDash,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
},
|
||||
}
|
||||
o.CurrencyPairs.Store(asset.Spot, fmt2)
|
||||
o.CurrencyPairs.Store(asset.Index, fmt2)
|
||||
|
||||
spot := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: delimiterDash,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: delimiterDash,
|
||||
},
|
||||
}
|
||||
o.CurrencyPairs.Store(asset.Spot, spot)
|
||||
o.CurrencyPairs.Store(asset.Index, index)
|
||||
|
||||
o.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
@@ -159,19 +176,25 @@ func (o *OKEX) Start(wg *sync.WaitGroup) {
|
||||
// Run implements the OKEX wrapper
|
||||
func (o *OKEX) Run() {
|
||||
if o.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.API.Endpoints.WebsocketURL)
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Websocket: %s. (url: %s).\n",
|
||||
o.Name,
|
||||
common.IsEnabled(o.Websocket.IsEnabled()),
|
||||
o.API.Endpoints.WebsocketURL)
|
||||
}
|
||||
|
||||
if o.Config.CurrencyPairs.Pairs[asset.Spot].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Spot].RequestFormat == nil ||
|
||||
o.Config.CurrencyPairs.Pairs[asset.Index].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Index].RequestFormat == nil {
|
||||
if o.Config.CurrencyPairs.Pairs[asset.Spot].ConfigFormat == nil ||
|
||||
o.Config.CurrencyPairs.Pairs[asset.Spot].RequestFormat == nil ||
|
||||
o.Config.CurrencyPairs.Pairs[asset.Index].ConfigFormat == nil ||
|
||||
o.Config.CurrencyPairs.Pairs[asset.Index].RequestFormat == nil {
|
||||
currFmt := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
Delimiter: delimiterDash,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
Delimiter: delimiterDash,
|
||||
},
|
||||
}
|
||||
o.CurrencyPairs.Store(asset.Spot, currFmt)
|
||||
@@ -180,16 +203,18 @@ func (o *OKEX) Run() {
|
||||
o.Config.CurrencyPairs.Store(asset.Index, currFmt)
|
||||
}
|
||||
|
||||
if o.Config.CurrencyPairs.Pairs[asset.Futures].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.Futures].RequestFormat == nil ||
|
||||
o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].ConfigFormat == nil || o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].RequestFormat == nil {
|
||||
if o.Config.CurrencyPairs.Pairs[asset.Futures].ConfigFormat == nil ||
|
||||
o.Config.CurrencyPairs.Pairs[asset.Futures].RequestFormat == nil ||
|
||||
o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].ConfigFormat == nil ||
|
||||
o.Config.CurrencyPairs.Pairs[asset.PerpetualSwap].RequestFormat == nil {
|
||||
currFmt := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
Delimiter: delimiterDash,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "_",
|
||||
Delimiter: delimiterUnderscore,
|
||||
},
|
||||
}
|
||||
o.CurrencyPairs.Store(asset.Futures, currFmt)
|
||||
@@ -198,14 +223,18 @@ func (o *OKEX) Run() {
|
||||
o.Config.CurrencyPairs.Store(asset.PerpetualSwap, currFmt)
|
||||
}
|
||||
|
||||
if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(), o.CurrencyPairs.Pairs[asset.Spot].RequestFormat.Delimiter) {
|
||||
if !common.StringDataContains(o.Config.CurrencyPairs.Pairs[asset.Spot].Enabled.Strings(),
|
||||
o.CurrencyPairs.Pairs[asset.Spot].RequestFormat.Delimiter) {
|
||||
enabledPairs := currency.NewPairsFromStrings([]string{"EOS-USDT"})
|
||||
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.",
|
||||
o.Name)
|
||||
|
||||
err := o.UpdatePairs(enabledPairs, asset.Spot, true, true)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", o.GetName())
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update currencies.\n",
|
||||
o.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -216,7 +245,10 @@ func (o *OKEX) Run() {
|
||||
|
||||
err := o.UpdateTradablePairs(false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", o.Name, err)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s failed to update tradable pairs. Err: %s",
|
||||
o.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +263,10 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) {
|
||||
}
|
||||
|
||||
for x := range prods {
|
||||
pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].BaseCurrency, o.GetPairFormat(i, false).Delimiter, prods[x].QuoteCurrency))
|
||||
pairs = append(pairs,
|
||||
currency.NewPairWithDelimiter(prods[x].BaseCurrency,
|
||||
prods[x].QuoteCurrency,
|
||||
o.GetPairFormat(i, false).Delimiter).String())
|
||||
}
|
||||
return pairs, nil
|
||||
case asset.Futures:
|
||||
@@ -240,9 +275,10 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pairs []string
|
||||
for x := range prods {
|
||||
pairs = append(pairs, fmt.Sprintf("%v%v%v", prods[x].UnderlyingIndex+prods[x].QuoteCurrency, o.GetPairFormat(i, false).Delimiter, prods[x].Delivery))
|
||||
p := strings.Split(prods[x].InstrumentID, delimiterDash)
|
||||
pairs = append(pairs,
|
||||
p[0]+delimiterDash+p[1]+o.GetPairFormat(i, false).Delimiter+p[2])
|
||||
}
|
||||
return pairs, nil
|
||||
|
||||
@@ -252,13 +288,18 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pairs []string
|
||||
for x := range prods {
|
||||
pairs = append(pairs, fmt.Sprintf("%v%v%v%vSWAP", prods[x].UnderlyingIndex, o.GetPairFormat(i, false).Delimiter, prods[x].QuoteCurrency, o.GetPairFormat(i, false).Delimiter))
|
||||
pairs = append(pairs,
|
||||
prods[x].UnderlyingIndex+
|
||||
delimiterDash+
|
||||
prods[x].QuoteCurrency+
|
||||
o.GetPairFormat(i, false).Delimiter+
|
||||
"SWAP")
|
||||
}
|
||||
return pairs, nil
|
||||
case asset.Index:
|
||||
return []string{fmt.Sprintf("BTC%vUSD", o.GetPairFormat(i, false).Delimiter)}, nil
|
||||
// This is updated in futures index
|
||||
return nil, errors.New("index updated in futures")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s invalid asset type", o.Name)
|
||||
@@ -268,13 +309,33 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) {
|
||||
// them in the exchanges config
|
||||
func (o *OKEX) UpdateTradablePairs(forceUpdate bool) error {
|
||||
for x := range o.CurrencyPairs.AssetTypes {
|
||||
a := o.CurrencyPairs.AssetTypes[x]
|
||||
pairs, err := o.FetchTradablePairs(a)
|
||||
if o.CurrencyPairs.AssetTypes[x] == asset.Index {
|
||||
// Update from futures
|
||||
continue
|
||||
}
|
||||
|
||||
pairs, err := o.FetchTradablePairs(o.CurrencyPairs.AssetTypes[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = o.UpdatePairs(currency.NewPairsFromStrings(pairs), a, false, forceUpdate)
|
||||
if o.CurrencyPairs.AssetTypes[x] == asset.Futures {
|
||||
var indexPairs []string
|
||||
for i := range pairs {
|
||||
indexPairs = append(indexPairs,
|
||||
strings.Split(pairs[i], delimiterUnderscore)[0])
|
||||
}
|
||||
err = o.UpdatePairs(currency.NewPairsFromStrings(indexPairs),
|
||||
asset.Index,
|
||||
false,
|
||||
forceUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = o.UpdatePairs(currency.NewPairsFromStrings(pairs),
|
||||
o.CurrencyPairs.AssetTypes[x], false, forceUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -291,92 +352,98 @@ func (o *OKEX) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price
|
||||
if err != nil {
|
||||
return tickerData, err
|
||||
}
|
||||
pairs := o.GetEnabledPairs(assetType)
|
||||
for i := range pairs {
|
||||
for j := range resp {
|
||||
if !pairs[i].Equal(resp[j].InstrumentID) {
|
||||
continue
|
||||
}
|
||||
tickerData = ticker.Price{
|
||||
Last: resp[j].Last,
|
||||
High: resp[j].High24h,
|
||||
Low: resp[j].Low24h,
|
||||
Bid: resp[j].BestBid,
|
||||
Ask: resp[j].BestAsk,
|
||||
Volume: resp[j].BaseVolume24h,
|
||||
QuoteVolume: resp[j].QuoteVolume24h,
|
||||
Open: resp[j].Open24h,
|
||||
Pair: pairs[i],
|
||||
LastUpdated: resp[j].Timestamp,
|
||||
}
|
||||
err = ticker.ProcessTicker(o.Name, &tickerData, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
for j := range resp {
|
||||
if !o.GetEnabledPairs(assetType).Contains(resp[j].InstrumentID, true) {
|
||||
continue
|
||||
}
|
||||
tickerData = ticker.Price{
|
||||
Last: resp[j].Last,
|
||||
High: resp[j].High24h,
|
||||
Low: resp[j].Low24h,
|
||||
Bid: resp[j].BestBid,
|
||||
Ask: resp[j].BestAsk,
|
||||
Volume: resp[j].BaseVolume24h,
|
||||
QuoteVolume: resp[j].QuoteVolume24h,
|
||||
Open: resp[j].Open24h,
|
||||
Pair: resp[j].InstrumentID,
|
||||
LastUpdated: resp[j].Timestamp,
|
||||
}
|
||||
err = ticker.ProcessTicker(o.Name, &tickerData, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
}
|
||||
|
||||
case asset.PerpetualSwap:
|
||||
resp, err := o.GetAllSwapTokensInformation()
|
||||
if err != nil {
|
||||
return tickerData, err
|
||||
}
|
||||
pairs := o.GetEnabledPairs(assetType)
|
||||
for i := range pairs {
|
||||
for j := range resp {
|
||||
if !pairs[i].Equal(resp[j].InstrumentID) {
|
||||
continue
|
||||
}
|
||||
tickerData = ticker.Price{
|
||||
Last: resp[j].Last,
|
||||
High: resp[j].High24H,
|
||||
Low: resp[j].Low24H,
|
||||
Bid: resp[j].BestBid,
|
||||
Ask: resp[j].BestAsk,
|
||||
Volume: resp[j].Volume24H,
|
||||
Pair: resp[j].InstrumentID,
|
||||
LastUpdated: resp[j].Timestamp,
|
||||
}
|
||||
err = ticker.ProcessTicker(o.Name, &tickerData, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
|
||||
for j := range resp {
|
||||
p := strings.Split(resp[j].InstrumentID, delimiterDash)
|
||||
nC := currency.NewPairWithDelimiter(p[0]+delimiterDash+p[1],
|
||||
p[2],
|
||||
delimiterUnderscore)
|
||||
if !o.GetEnabledPairs(assetType).Contains(nC, true) {
|
||||
continue
|
||||
}
|
||||
tickerData = ticker.Price{
|
||||
Last: resp[j].Last,
|
||||
High: resp[j].High24H,
|
||||
Low: resp[j].Low24H,
|
||||
Bid: resp[j].BestBid,
|
||||
Ask: resp[j].BestAsk,
|
||||
Volume: resp[j].Volume24H,
|
||||
Pair: nC,
|
||||
LastUpdated: resp[j].Timestamp,
|
||||
}
|
||||
err = ticker.ProcessTicker(o.Name, &tickerData, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
}
|
||||
|
||||
case asset.Futures:
|
||||
resp, err := o.GetAllFuturesTokenInfo()
|
||||
if err != nil {
|
||||
return tickerData, err
|
||||
}
|
||||
pairs := o.GetEnabledPairs(assetType)
|
||||
for i := range pairs {
|
||||
for j := range resp {
|
||||
if !pairs[i].Equal(resp[j].InstrumentID) {
|
||||
continue
|
||||
}
|
||||
tickerData = ticker.Price{
|
||||
Last: resp[j].Last,
|
||||
High: resp[j].High24h,
|
||||
Low: resp[j].Low24h,
|
||||
Bid: resp[j].BestBid,
|
||||
Ask: resp[j].BestAsk,
|
||||
Volume: resp[j].Volume24h,
|
||||
Pair: resp[j].InstrumentID,
|
||||
LastUpdated: resp[j].Timestamp,
|
||||
}
|
||||
err = ticker.ProcessTicker(o.Name, &tickerData, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
|
||||
for j := range resp {
|
||||
p := strings.Split(resp[j].InstrumentID, delimiterDash)
|
||||
nC := currency.NewPairWithDelimiter(p[0]+delimiterDash+p[1],
|
||||
p[2],
|
||||
delimiterUnderscore)
|
||||
if !o.GetEnabledPairs(assetType).Contains(nC, true) {
|
||||
continue
|
||||
}
|
||||
tickerData = ticker.Price{
|
||||
Last: resp[j].Last,
|
||||
High: resp[j].High24h,
|
||||
Low: resp[j].Low24h,
|
||||
Bid: resp[j].BestBid,
|
||||
Ask: resp[j].BestAsk,
|
||||
Volume: resp[j].Volume24h,
|
||||
Pair: nC,
|
||||
LastUpdated: resp[j].Timestamp,
|
||||
}
|
||||
err = ticker.ProcessTicker(o.Name, &tickerData, assetType)
|
||||
if err != nil {
|
||||
log.Error(log.Ticker, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ticker.GetTicker(o.GetName(), p, assetType)
|
||||
return ticker.GetTicker(o.Name, p, assetType)
|
||||
}
|
||||
|
||||
// FetchTicker returns the ticker for a currency pair
|
||||
func (o *OKEX) FetchTicker(p currency.Pair, assetType asset.Item) (tickerData ticker.Price, err error) {
|
||||
tickerData, err = ticker.GetTicker(o.GetName(), p, assetType)
|
||||
if assetType == asset.Index {
|
||||
return tickerData, errors.New("ticker fetching not supported for index")
|
||||
}
|
||||
tickerData, err = ticker.GetTicker(o.Name, p, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateTicker(p, assetType)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"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/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
@@ -307,11 +308,35 @@ func (o *OKGroup) GetSpotTokenPairDetails() (resp []GetSpotTokenPairDetailsRespo
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, OKGroupInstruments, nil, &resp, false)
|
||||
}
|
||||
|
||||
// GetSpotOrderBook Getting the order book of a trading pair. Pagination is not supported here.
|
||||
// The whole book will be returned for one request. Websocket is recommended here.
|
||||
func (o *OKGroup) GetSpotOrderBook(request GetSpotOrderBookRequest) (resp GetSpotOrderBookResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupGetSpotOrderBook, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, false)
|
||||
// GetOrderBook Getting the order book of a trading pair. Pagination is not
|
||||
// supported here. The whole book will be returned for one request. Websocket is
|
||||
// recommended here.
|
||||
func (o *OKGroup) GetOrderBook(request GetOrderBookRequest, a asset.Item) (resp GetOrderBookResponse, _ error) {
|
||||
var requestType, endpoint string
|
||||
switch a {
|
||||
case asset.Spot:
|
||||
endpoint = OKGroupGetSpotOrderBook
|
||||
requestType = okGroupTokenSubsection
|
||||
case asset.Futures:
|
||||
endpoint = OKGroupGetSpotOrderBook
|
||||
requestType = "futures"
|
||||
case asset.PerpetualSwap:
|
||||
endpoint = "depth"
|
||||
requestType = "swap"
|
||||
default:
|
||||
return resp, errors.New("unhandled asset type")
|
||||
}
|
||||
requestURL := fmt.Sprintf("%v/%v/%v/%v",
|
||||
OKGroupInstruments,
|
||||
request.InstrumentID,
|
||||
endpoint,
|
||||
FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet,
|
||||
requestType,
|
||||
requestURL,
|
||||
nil,
|
||||
&resp,
|
||||
false)
|
||||
}
|
||||
|
||||
// GetSpotAllTokenPairsInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all trading pairs.
|
||||
|
||||
78
exchanges/okgroup/okgroup_test.go
Normal file
78
exchanges/okgroup/okgroup_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package okgroup
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"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/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
const (
|
||||
apiKey = ""
|
||||
apiSecret = ""
|
||||
|
||||
testAPIURL = "https://www.okex.com/api/"
|
||||
testAPIVersion = "/v3/"
|
||||
)
|
||||
|
||||
var o OKGroup
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig("../../testdata/configtest.json", true)
|
||||
if err != nil {
|
||||
log.Fatal("okgroup load config error", err)
|
||||
}
|
||||
okgroup, err := cfg.GetExchangeConfig("Okex")
|
||||
if err != nil {
|
||||
log.Fatal("okgroup Setup() init error", err)
|
||||
}
|
||||
|
||||
okgroup.API.AuthenticatedSupport = true
|
||||
okgroup.API.Credentials.Key = apiKey
|
||||
okgroup.API.Credentials.Secret = apiSecret
|
||||
o.API.Endpoints.URL = testAPIURL
|
||||
o.APIVersion = testAPIVersion
|
||||
|
||||
o.Requester = request.New("okgroup_test_things",
|
||||
request.NewRateLimit(time.Second, 10),
|
||||
request.NewRateLimit(time.Second, 10),
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout),
|
||||
)
|
||||
o.Websocket = wshandler.New()
|
||||
|
||||
err = o.Setup(okgroup)
|
||||
if err != nil {
|
||||
log.Fatal("okgroup setup error", err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestGetOrderbook(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := o.GetOrderBook(GetOrderBookRequest{InstrumentID: "BTC-USDT"},
|
||||
asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// futures expire and break test, will need to mock this in the future
|
||||
_, err = o.GetOrderBook(GetOrderBookRequest{InstrumentID: "Payload"},
|
||||
asset.Futures)
|
||||
if err == nil {
|
||||
t.Error("error cannot be nil")
|
||||
}
|
||||
|
||||
_, err = o.GetOrderBook(GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"},
|
||||
asset.PerpetualSwap)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -274,15 +274,15 @@ type GetSpotTokenPairDetailsResponse struct {
|
||||
TickSize string `json:"tick_size"`
|
||||
}
|
||||
|
||||
// GetSpotOrderBookRequest request data for GetSpotOrderBook
|
||||
type GetSpotOrderBookRequest struct {
|
||||
// GetOrderBookRequest request data for GetOrderBook
|
||||
type GetOrderBookRequest struct {
|
||||
Size int64 `url:"size,string,omitempty"` // [optional] number of results per request. Maximum 200
|
||||
Depth float64 `url:"depth,string,omitempty"` // [optional] the aggregation of the book. e.g . 0.1,0.001
|
||||
InstrumentID string `url:"-"` // [required] trading pairs
|
||||
}
|
||||
|
||||
// GetSpotOrderBookResponse response data for GetSpotOrderBook
|
||||
type GetSpotOrderBookResponse struct {
|
||||
// GetOrderBookResponse response data
|
||||
type GetOrderBookResponse struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Asks [][]string `json:"asks"` // [[0]: "Price", [1]: "Size", [2]: "Num_orders"], ...
|
||||
Bids [][]string `json:"bids"` // [[0]: "Price", [1]: "Size", [2]: "Num_orders"], ...
|
||||
@@ -678,37 +678,16 @@ type GetFuturesContractInformationResponse struct {
|
||||
UnderlyingIndex string `json:"underlying_index"`
|
||||
}
|
||||
|
||||
// GetFuturesOrderBookRequest request data for GetFuturesOrderBook
|
||||
type GetFuturesOrderBookRequest struct {
|
||||
InstrumentID string `url:"-"` // [required] Contract ID, e.g. "BTC-USD-180213"
|
||||
Size int64 `url:"size,omitempty"` // [optional] The size of the price range (max: 200)
|
||||
}
|
||||
|
||||
// FuturesOrderbookItem stores an individual futures orderbook item
|
||||
type FuturesOrderbookItem struct {
|
||||
Price float64
|
||||
Size int64
|
||||
ForceLiquidatedOrders int64 // Number of force liquidated orders
|
||||
NumberOrders int64 // Number of orders on the price
|
||||
}
|
||||
|
||||
// GetFuturesOrderBookResponse response data for GetFuturesOrderBook
|
||||
type GetFuturesOrderBookResponse struct {
|
||||
Asks []FuturesOrderbookItem
|
||||
Bids []FuturesOrderbookItem
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// GetFuturesTokenInfoResponse response data for GetFuturesOrderBook
|
||||
type GetFuturesTokenInfoResponse struct {
|
||||
BestAsk float64 `json:"best_ask,string"`
|
||||
BestBid float64 `json:"best_bid,string"`
|
||||
High24h float64 `json:"high_24h,string"`
|
||||
InstrumentID currency.Pair `json:"instrument_id"`
|
||||
Last float64 `json:"last,string"`
|
||||
Low24h float64 `json:"low_24h,string"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Volume24h float64 `json:"volume_24h,string"`
|
||||
BestAsk float64 `json:"best_ask,string"`
|
||||
BestBid float64 `json:"best_bid,string"`
|
||||
High24h float64 `json:"high_24h,string"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
Last float64 `json:"last,string"`
|
||||
Low24h float64 `json:"low_24h,string"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Volume24h float64 `json:"volume_24h,string"`
|
||||
}
|
||||
|
||||
// GetFuturesFilledOrderRequest request data for GetFuturesFilledOrder
|
||||
@@ -1059,14 +1038,14 @@ type GetSwapOrderBookResponse struct {
|
||||
|
||||
// GetAllSwapTokensInformationResponse response data for GetAllSwapTokensInformation
|
||||
type GetAllSwapTokensInformationResponse struct {
|
||||
InstrumentID currency.Pair `json:"instrument_id"`
|
||||
Last float64 `json:"last,string"`
|
||||
High24H float64 `json:"high_24h,string"`
|
||||
Low24H float64 `json:"low_24h,string"`
|
||||
BestBid float64 `json:"best_bid,string"`
|
||||
BestAsk float64 `json:"best_ask,string"`
|
||||
Volume24H float64 `json:"volume_24h,string"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
Last float64 `json:"last,string"`
|
||||
High24H float64 `json:"high_24h,string"`
|
||||
Low24H float64 `json:"low_24h,string"`
|
||||
BestBid float64 `json:"best_bid,string"`
|
||||
BestAsk float64 `json:"best_ask,string"`
|
||||
Volume24H float64 `json:"volume_24h,string"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// GetSwapFilledOrdersDataRequest request data for GetSwapFilledOrdersData
|
||||
|
||||
@@ -140,11 +140,36 @@ const (
|
||||
okGroupWsFuturesOrder = okGroupWsFuturesSubsection + okGroupWsOrder
|
||||
|
||||
okGroupWsRateLimit = 30
|
||||
|
||||
allowableIterations = 25
|
||||
delimiterColon = ":"
|
||||
delimiterDash = "-"
|
||||
delimiterUnderscore = "_"
|
||||
)
|
||||
|
||||
// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time
|
||||
// orderbookMutex Ensures if two entries arrive at once, only one can be
|
||||
// processed at a time
|
||||
var orderbookMutex sync.Mutex
|
||||
var defaultSubscribedChannels = []string{okGroupWsSpotDepth, okGroupWsSpotCandle300s, okGroupWsSpotTicker, okGroupWsSpotTrade}
|
||||
|
||||
var defaultSpotSubscribedChannels = []string{okGroupWsSpotDepth,
|
||||
okGroupWsSpotCandle300s,
|
||||
okGroupWsSpotTicker,
|
||||
okGroupWsSpotTrade}
|
||||
|
||||
var defaultFuturesSubscribedChannels = []string{okGroupWsFuturesDepth,
|
||||
okGroupWsFuturesCandle300s,
|
||||
okGroupWsFuturesTicker,
|
||||
okGroupWsFuturesTrade}
|
||||
|
||||
var defaultIndexSubscribedChannels = []string{okGroupWsIndexCandle300s,
|
||||
okGroupWsIndexTicker}
|
||||
|
||||
var defaultSwapSubscribedChannels = []string{okGroupWsSwapDepth,
|
||||
okGroupWsSwapCandle300s,
|
||||
okGroupWsSwapTicker,
|
||||
okGroupWsSwapTrade,
|
||||
okGroupWsSwapFundingRate,
|
||||
okGroupWsSwapMarkPrice}
|
||||
|
||||
// WsConnect initiates a websocket connection
|
||||
func (o *OKGroup) WsConnect() error {
|
||||
@@ -161,13 +186,15 @@ func (o *OKGroup) WsConnect() error {
|
||||
o.Websocket.GetWebsocketURL())
|
||||
}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
wg.Add(1)
|
||||
go o.WsHandleData(&wg)
|
||||
go o.wsPingHandler(&wg)
|
||||
if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
err = o.WsLogin()
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", o.Name, err)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%v - authentication failed: %v\n",
|
||||
o.Name,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,36 +204,6 @@ func (o *OKGroup) WsConnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsPingHandler sends a message "ping" every 27 to maintain the connection to the websocket
|
||||
func (o *OKGroup) wsPingHandler(wg *sync.WaitGroup) {
|
||||
o.Websocket.Wg.Add(1)
|
||||
defer o.Websocket.Wg.Done()
|
||||
|
||||
ticker := time.NewTicker(time.Second * 27)
|
||||
defer ticker.Stop()
|
||||
|
||||
wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-o.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
if !o.Websocket.IsConnected() {
|
||||
continue
|
||||
}
|
||||
err := o.WebsocketConn.Connection.WriteMessage(websocket.TextMessage, []byte("ping"))
|
||||
if o.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v sending ping", o.GetName())
|
||||
}
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WsHandleData handles the read data from the websocket connection
|
||||
func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) {
|
||||
o.Websocket.Wg.Add(1)
|
||||
@@ -240,7 +237,11 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) {
|
||||
err = common.JSONDecode(resp.Raw, &errorResponse)
|
||||
if err == nil && errorResponse.ErrorCode > 0 {
|
||||
if o.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "WS Error Event: %v Message: %v", errorResponse.Event, errorResponse.Message)
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"WS Error Event: %v Message: %v for %s",
|
||||
errorResponse.Event,
|
||||
errorResponse.Message,
|
||||
o.Name)
|
||||
}
|
||||
o.WsHandleErrorResponse(errorResponse)
|
||||
continue
|
||||
@@ -252,10 +253,12 @@ func (o *OKGroup) WsHandleData(wg *sync.WaitGroup) {
|
||||
o.Websocket.SetCanUseAuthenticatedEndpoints(eventResponse.Success)
|
||||
}
|
||||
if o.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "WS Event: %v on Channel: %v", eventResponse.Event, eventResponse.Channel)
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"WS Event: %v on Channel: %v for %s",
|
||||
eventResponse.Event,
|
||||
eventResponse.Channel,
|
||||
o.Name)
|
||||
}
|
||||
o.Websocket.DataHandler <- eventResponse
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +276,10 @@ func (o *OKGroup) WsLogin() error {
|
||||
base64 := crypto.Base64Encode(hmac)
|
||||
request := WebsocketEventRequest{
|
||||
Operation: "login",
|
||||
Arguments: []string{o.API.Credentials.Key, o.API.Credentials.ClientID, fmt.Sprintf("%v", unixTime), base64},
|
||||
Arguments: []string{o.API.Credentials.Key,
|
||||
o.API.Credentials.ClientID,
|
||||
fmt.Sprintf("%v", unixTime),
|
||||
base64},
|
||||
}
|
||||
err := o.WebsocketConn.SendMessage(request)
|
||||
if err != nil {
|
||||
@@ -286,7 +292,9 @@ func (o *OKGroup) WsLogin() error {
|
||||
// WsHandleErrorResponse sends an error message to ws handler
|
||||
func (o *OKGroup) WsHandleErrorResponse(event WebsocketErrorResponse) {
|
||||
errorMessage := fmt.Sprintf("%v error - %v message: %s ",
|
||||
o.GetName(), event.ErrorCode, event.Message)
|
||||
o.Name,
|
||||
event.ErrorCode,
|
||||
event.Message)
|
||||
if o.Verbose {
|
||||
log.Error(log.ExchangeSys, errorMessage)
|
||||
}
|
||||
@@ -314,28 +322,54 @@ func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string {
|
||||
// eg "spot/ticker:BTCUSD" results in "SPOT"
|
||||
func (o *OKGroup) GetAssetTypeFromTableName(table string) asset.Item {
|
||||
assetIndex := strings.Index(table, "/")
|
||||
return asset.Item(table[:assetIndex])
|
||||
switch table[:assetIndex] {
|
||||
case asset.Futures.String():
|
||||
return asset.Futures
|
||||
case asset.Spot.String():
|
||||
return asset.Spot
|
||||
case "swap":
|
||||
return asset.PerpetualSwap
|
||||
case asset.Index.String():
|
||||
return asset.Index
|
||||
default:
|
||||
log.Warnf(log.ExchangeSys, "%s unhandled asset type %s",
|
||||
o.Name,
|
||||
table[:assetIndex])
|
||||
return asset.Item(table[:assetIndex])
|
||||
}
|
||||
}
|
||||
|
||||
// WsHandleDataResponse classifies the WS response and sends to appropriate handler
|
||||
func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) {
|
||||
switch o.GetWsChannelWithoutOrderType(response.Table) {
|
||||
|
||||
case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s, okGroupWsCandle900s,
|
||||
okGroupWsCandle1800s, okGroupWsCandle3600s, okGroupWsCandle7200s, okGroupWsCandle14400s,
|
||||
okGroupWsCandle21600s, okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s:
|
||||
case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s,
|
||||
okGroupWsCandle900s, okGroupWsCandle1800s, okGroupWsCandle3600s,
|
||||
okGroupWsCandle7200s, okGroupWsCandle14400s, okGroupWsCandle21600s,
|
||||
okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s:
|
||||
o.wsProcessCandles(response)
|
||||
case okGroupWsDepth, okGroupWsDepth5:
|
||||
// Locking, orderbooks cannot be processed out of order
|
||||
orderbookMutex.Lock()
|
||||
err := o.WsProcessOrderBook(response)
|
||||
if err != nil {
|
||||
pair := currency.NewPairDelimiter(response.Data[0].InstrumentID, "-")
|
||||
channelToResubscribe := wshandler.WebsocketChannelSubscription{
|
||||
Channel: response.Table,
|
||||
Currency: pair,
|
||||
for i := range response.Data {
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterDash)
|
||||
default:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
channelToResubscribe := wshandler.WebsocketChannelSubscription{
|
||||
Channel: response.Table,
|
||||
Currency: c,
|
||||
}
|
||||
o.Websocket.ResubscribeToChannel(channelToResubscribe)
|
||||
}
|
||||
o.Websocket.ResubscribeToChannel(channelToResubscribe)
|
||||
}
|
||||
orderbookMutex.Unlock()
|
||||
case okGroupWsTicker:
|
||||
@@ -343,26 +377,37 @@ func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) {
|
||||
case okGroupWsTrade:
|
||||
o.wsProcessTrades(response)
|
||||
default:
|
||||
logDataResponse(response)
|
||||
logDataResponse(response, o.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// logDataResponse will log the details of any websocket data event
|
||||
// where there is no websocket datahandler for it
|
||||
func logDataResponse(response *WebsocketDataResponse) {
|
||||
func logDataResponse(response *WebsocketDataResponse, exchangeName string) {
|
||||
for i := range response.Data {
|
||||
log.Errorf(log.ExchangeSys, "Unhandled channel: '%v'. Instrument '%v' Timestamp '%v', Data '%v",
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s Unhandled channel: '%v'. Instrument '%v' Timestamp '%v'",
|
||||
exchangeName,
|
||||
response.Table,
|
||||
response.Data[i].InstrumentID,
|
||||
response.Data[i].Timestamp,
|
||||
response.Data[i])
|
||||
response.Data[i].Timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
// wsProcessTickers converts ticker data and sends it to the datahandler
|
||||
func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
|
||||
for i := range response.Data {
|
||||
instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-")
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore)
|
||||
default:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Exchange: o.Name,
|
||||
Open: response.Data[i].Open24h,
|
||||
@@ -376,7 +421,7 @@ func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
|
||||
Last: response.Data[i].Last,
|
||||
Timestamp: response.Data[i].Timestamp,
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
Pair: instrument,
|
||||
Pair: c,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,11 +429,21 @@ func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
|
||||
// wsProcessTrades converts trade data and sends it to the datahandler
|
||||
func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) {
|
||||
for i := range response.Data {
|
||||
instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-")
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore)
|
||||
default:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Amount: response.Data[i].Size,
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
CurrencyPair: instrument,
|
||||
CurrencyPair: c,
|
||||
EventTime: time.Now().Unix(),
|
||||
Exchange: o.GetName(),
|
||||
Price: response.Data[i].WebsocketTradeResponse.Price,
|
||||
@@ -401,10 +456,24 @@ func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) {
|
||||
// wsProcessCandles converts candle data and sends it to the data handler
|
||||
func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
|
||||
for i := range response.Data {
|
||||
instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-")
|
||||
timeData, err := time.Parse(time.RFC3339Nano, response.Data[i].WebsocketCandleResponse.Candle[0])
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore)
|
||||
default:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
timeData, err := time.Parse(time.RFC3339Nano,
|
||||
response.Data[i].WebsocketCandleResponse.Candle[0])
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%v Time data could not be parsed: %v", o.GetName(), response.Data[i].Candle[0])
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%v Time data could not be parsed: %v",
|
||||
o.Name,
|
||||
response.Data[i].Candle[0])
|
||||
}
|
||||
|
||||
candleIndex := strings.LastIndex(response.Table, okGroupWsCandle)
|
||||
@@ -416,16 +485,36 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
|
||||
|
||||
klineData := wshandler.KlineData{
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
Pair: instrument,
|
||||
Pair: c,
|
||||
Exchange: o.GetName(),
|
||||
Timestamp: timeData,
|
||||
Interval: candleInterval,
|
||||
}
|
||||
klineData.OpenPrice, _ = strconv.ParseFloat(response.Data[i].Candle[1], 64)
|
||||
klineData.HighPrice, _ = strconv.ParseFloat(response.Data[i].Candle[2], 64)
|
||||
klineData.LowPrice, _ = strconv.ParseFloat(response.Data[i].Candle[3], 64)
|
||||
klineData.ClosePrice, _ = strconv.ParseFloat(response.Data[i].Candle[4], 64)
|
||||
klineData.Volume, _ = strconv.ParseFloat(response.Data[i].Candle[5], 64)
|
||||
klineData.OpenPrice, err = strconv.ParseFloat(response.Data[i].Candle[1], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
klineData.HighPrice, err = strconv.ParseFloat(response.Data[i].Candle[2], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
klineData.LowPrice, err = strconv.ParseFloat(response.Data[i].Candle[3], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
klineData.ClosePrice, err = strconv.ParseFloat(response.Data[i].Candle[4], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
klineData.Volume, err = strconv.ParseFloat(response.Data[i].Candle[5], 64)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- klineData
|
||||
}
|
||||
@@ -434,57 +523,96 @@ func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
|
||||
// WsProcessOrderBook Validates the checksum and updates internal orderbook values
|
||||
func (o *OKGroup) WsProcessOrderBook(response *WebsocketDataResponse) (err error) {
|
||||
for i := range response.Data {
|
||||
instrument := currency.NewPairDelimiter(response.Data[i].InstrumentID, "-")
|
||||
a := o.GetAssetTypeFromTableName(response.Table)
|
||||
var c currency.Pair
|
||||
switch a {
|
||||
case asset.Futures, asset.PerpetualSwap:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore)
|
||||
default:
|
||||
f := strings.Split(response.Data[i].InstrumentID, delimiterDash)
|
||||
c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash)
|
||||
}
|
||||
|
||||
if response.Action == okGroupWsOrderbookPartial {
|
||||
err = o.WsProcessPartialOrderBook(&response.Data[i], instrument, response.Table)
|
||||
err = o.WsProcessPartialOrderBook(&response.Data[i], c, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if response.Action == okGroupWsOrderbookUpdate {
|
||||
err = o.WsProcessUpdateOrderbook(&response.Data[i], instrument, response.Table)
|
||||
if len(response.Data[i].Asks) == 0 && len(response.Data[i].Bids) == 0 {
|
||||
continue
|
||||
}
|
||||
err = o.WsProcessUpdateOrderbook(&response.Data[i], c, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AppendWsOrderbookItems adds websocket orderbook data bid/asks into an orderbook item array
|
||||
func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) (orderbookItems []orderbook.Item) {
|
||||
func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.Item, error) {
|
||||
var items []orderbook.Item
|
||||
for j := range entries {
|
||||
amount, _ := strconv.ParseFloat(entries[j][1].(string), 64)
|
||||
price, _ := strconv.ParseFloat(entries[j][0].(string), 64)
|
||||
orderbookItems = append(orderbookItems, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
amount, err := strconv.ParseFloat(entries[j][1].(string), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
price, err := strconv.ParseFloat(entries[j][0].(string), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, orderbook.Item{Amount: amount, Price: price})
|
||||
}
|
||||
return
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// WsProcessPartialOrderBook takes websocket orderbook data and creates an orderbook
|
||||
// Calculates checksum to ensure it is valid
|
||||
func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, tableName string) error {
|
||||
func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, a asset.Item) error {
|
||||
signedChecksum := o.CalculatePartialOrderbookChecksum(wsEventData)
|
||||
if signedChecksum != wsEventData.Checksum {
|
||||
return fmt.Errorf("channel: %v. Orderbook partial for %v checksum invalid", tableName, instrument)
|
||||
return fmt.Errorf("%s channel: %s. Orderbook partial for %v checksum invalid",
|
||||
o.Name,
|
||||
a,
|
||||
instrument)
|
||||
}
|
||||
if o.Verbose {
|
||||
log.Debug(log.ExchangeSys, "Passed checksum!")
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s passed checksum for instrument %s",
|
||||
o.Name,
|
||||
instrument)
|
||||
}
|
||||
asks := o.AppendWsOrderbookItems(wsEventData.Asks)
|
||||
bids := o.AppendWsOrderbookItems(wsEventData.Bids)
|
||||
|
||||
asks, err := o.AppendWsOrderbookItems(wsEventData.Asks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bids, err := o.AppendWsOrderbookItems(wsEventData.Bids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newOrderBook := orderbook.Base{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
AssetType: o.GetAssetTypeFromTableName(tableName),
|
||||
AssetType: a,
|
||||
LastUpdated: wsEventData.Timestamp,
|
||||
Pair: instrument,
|
||||
ExchangeName: o.GetName(),
|
||||
}
|
||||
|
||||
err := o.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
err = o.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: o.GetName(),
|
||||
Asset: o.GetAssetTypeFromTableName(tableName),
|
||||
Asset: a,
|
||||
Pair: instrument,
|
||||
}
|
||||
return nil
|
||||
@@ -492,123 +620,209 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i
|
||||
|
||||
// WsProcessUpdateOrderbook updates an existing orderbook using websocket data
|
||||
// After merging WS data, it will sort, validate and finally update the existing orderbook
|
||||
func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, tableName string) error {
|
||||
func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, a asset.Item) error {
|
||||
update := wsorderbook.WebsocketOrderbookUpdate{
|
||||
AssetType: asset.Spot,
|
||||
CurrencyPair: instrument,
|
||||
UpdateTime: wsEventData.Timestamp,
|
||||
Asset: a,
|
||||
Pair: instrument,
|
||||
UpdateTime: wsEventData.Timestamp,
|
||||
}
|
||||
update.Asks = o.AppendWsOrderbookItems(wsEventData.Asks)
|
||||
update.Bids = o.AppendWsOrderbookItems(wsEventData.Bids)
|
||||
err := o.Websocket.Orderbook.Update(&update)
|
||||
if err != nil {
|
||||
log.Error(log.ExchangeSys, err)
|
||||
}
|
||||
updatedOb := o.Websocket.Orderbook.GetOrderbook(instrument, asset.Spot)
|
||||
checksum := o.CalculateUpdateOrderbookChecksum(updatedOb)
|
||||
if checksum == wsEventData.Checksum {
|
||||
if o.Verbose {
|
||||
log.Debug(log.ExchangeSys, "Orderbook valid")
|
||||
}
|
||||
o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: o.GetName(),
|
||||
Asset: o.GetAssetTypeFromTableName(tableName),
|
||||
Pair: instrument,
|
||||
}
|
||||
|
||||
} else {
|
||||
if o.Verbose {
|
||||
log.Warnln(log.ExchangeSys, "Orderbook invalid")
|
||||
}
|
||||
return fmt.Errorf("channel: %v. Orderbook update for %v checksum invalid. Received %v Calculated %v", tableName, instrument, wsEventData.Checksum, checksum)
|
||||
var err error
|
||||
update.Asks, err = o.AppendWsOrderbookItems(wsEventData.Asks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
update.Bids, err = o.AppendWsOrderbookItems(wsEventData.Bids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = o.Websocket.Orderbook.Update(&update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedOb := o.Websocket.Orderbook.GetOrderbook(instrument, a)
|
||||
checksum := o.CalculateUpdateOrderbookChecksum(updatedOb)
|
||||
|
||||
if checksum != wsEventData.Checksum {
|
||||
// re-sub
|
||||
log.Warnf(log.ExchangeSys, "%s checksum failure for item %s",
|
||||
o.Name,
|
||||
wsEventData.InstrumentID)
|
||||
return errors.New("checksum failed")
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: o.GetName(),
|
||||
Asset: a,
|
||||
Pair: instrument,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculatePartialOrderbookChecksum alternates over the first 25 bid and ask entries from websocket data
|
||||
// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them
|
||||
// This will also work when there are less than 25 entries (for whatever reason)
|
||||
// CalculatePartialOrderbookChecksum alternates over the first 25 bid and ask
|
||||
// entries from websocket data. The checksum is made up of the price and the
|
||||
// quantity with a semicolon (:) deliminating them. This will also work when
|
||||
// there are less than 25 entries (for whatever reason)
|
||||
// eg Bid:Ask:Bid:Ask:Ask:Ask
|
||||
func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketDataWrapper) int32 {
|
||||
var checksum string
|
||||
iterations := 25
|
||||
for i := 0; i < iterations; i++ {
|
||||
bidsMessage := ""
|
||||
askMessage := ""
|
||||
for i := 0; i < allowableIterations; i++ {
|
||||
if len(orderbookData.Bids)-1 >= i {
|
||||
bidsMessage = fmt.Sprintf("%v:%v:", orderbookData.Bids[i][0], orderbookData.Bids[i][1])
|
||||
checksum += orderbookData.Bids[i][0].(string) +
|
||||
delimiterColon +
|
||||
orderbookData.Bids[i][1].(string) +
|
||||
delimiterColon
|
||||
}
|
||||
if len(orderbookData.Asks)-1 >= i {
|
||||
askMessage = fmt.Sprintf("%v:%v:", orderbookData.Asks[i][0], orderbookData.Asks[i][1])
|
||||
|
||||
}
|
||||
if checksum == "" {
|
||||
checksum = fmt.Sprintf("%v%v", bidsMessage, askMessage)
|
||||
} else {
|
||||
checksum = fmt.Sprintf("%v%v%v", checksum, bidsMessage, askMessage)
|
||||
checksum += orderbookData.Asks[i][0].(string) +
|
||||
delimiterColon +
|
||||
orderbookData.Asks[i][1].(string) +
|
||||
delimiterColon
|
||||
}
|
||||
}
|
||||
checksum = strings.TrimSuffix(checksum, ":")
|
||||
checksum = strings.TrimSuffix(checksum, delimiterColon)
|
||||
return int32(crc32.ChecksumIEEE([]byte(checksum)))
|
||||
}
|
||||
|
||||
// CalculateUpdateOrderbookChecksum alternates over the first 25 bid and ask entries of a merged orderbook
|
||||
// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them
|
||||
// This will also work when there are less than 25 entries (for whatever reason)
|
||||
// CalculateUpdateOrderbookChecksum alternates over the first 25 bid and ask
|
||||
// entries of a merged orderbook. The checksum is made up of the price and the
|
||||
// quantity with a semicolon (:) deliminating them. This will also work when
|
||||
// there are less than 25 entries (for whatever reason)
|
||||
// eg Bid:Ask:Bid:Ask:Ask:Ask
|
||||
func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base) int32 {
|
||||
var checksum string
|
||||
iterations := 25
|
||||
for i := 0; i < iterations; i++ {
|
||||
bidsMessage := ""
|
||||
askMessage := ""
|
||||
for i := 0; i < allowableIterations; i++ {
|
||||
if len(orderbookData.Bids)-1 >= i {
|
||||
price := strconv.FormatFloat(orderbookData.Bids[i].Price, 'f', -1, 64)
|
||||
amount := strconv.FormatFloat(orderbookData.Bids[i].Amount, 'f', -1, 64)
|
||||
bidsMessage = fmt.Sprintf("%v:%v:", price, amount)
|
||||
checksum += price + delimiterColon + amount + delimiterColon
|
||||
}
|
||||
if len(orderbookData.Asks)-1 >= i {
|
||||
price := strconv.FormatFloat(orderbookData.Asks[i].Price, 'f', -1, 64)
|
||||
amount := strconv.FormatFloat(orderbookData.Asks[i].Amount, 'f', -1, 64)
|
||||
askMessage = fmt.Sprintf("%v:%v:", price, amount)
|
||||
}
|
||||
if checksum == "" {
|
||||
checksum = fmt.Sprintf("%v%v", bidsMessage, askMessage)
|
||||
} else {
|
||||
checksum = fmt.Sprintf("%v%v%v", checksum, bidsMessage, askMessage)
|
||||
checksum += price + delimiterColon + amount + delimiterColon
|
||||
}
|
||||
}
|
||||
checksum = strings.TrimSuffix(checksum, ":")
|
||||
checksum = strings.TrimSuffix(checksum, delimiterColon)
|
||||
return int32(crc32.ChecksumIEEE([]byte(checksum)))
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be
|
||||
// handled by ManageSubscriptions()
|
||||
func (o *OKGroup) GenerateDefaultSubscriptions() {
|
||||
enabledCurrencies := o.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
defaultSubscribedChannels = append(defaultSubscribedChannels, okGroupWsSpotMarginAccount, okGroupWsSpotAccount, okGroupWsSpotOrder)
|
||||
}
|
||||
for i := range defaultSubscribedChannels {
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = "-"
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: defaultSubscribedChannels[i],
|
||||
Currency: enabledCurrencies[j],
|
||||
})
|
||||
assets := o.GetAssetTypes()
|
||||
for x := range assets {
|
||||
enabledCurrencies := o.GetEnabledPairs(assets[x])
|
||||
if len(enabledCurrencies) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch assets[x] {
|
||||
case asset.Spot:
|
||||
for i := range enabledCurrencies {
|
||||
for y := range defaultSpotSubscribedChannels {
|
||||
subscriptions = append(subscriptions,
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: defaultSpotSubscribedChannels[y],
|
||||
Currency: o.FormatExchangeCurrency(enabledCurrencies[i],
|
||||
asset.Spot),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
subscriptions = append(subscriptions,
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsSpotMarginAccount,
|
||||
},
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsSpotAccount,
|
||||
},
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsSpotOrder,
|
||||
})
|
||||
}
|
||||
case asset.Futures:
|
||||
for i := range enabledCurrencies {
|
||||
for y := range defaultFuturesSubscribedChannels {
|
||||
subscriptions = append(subscriptions,
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: defaultFuturesSubscribedChannels[y],
|
||||
Currency: o.FormatExchangeCurrency(enabledCurrencies[i],
|
||||
asset.Futures),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
subscriptions = append(subscriptions,
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsFuturesAccount,
|
||||
},
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsFuturesPosition,
|
||||
},
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsFuturesOrder,
|
||||
})
|
||||
}
|
||||
case asset.PerpetualSwap:
|
||||
for i := range enabledCurrencies {
|
||||
for y := range defaultSwapSubscribedChannels {
|
||||
subscriptions = append(subscriptions,
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: defaultSwapSubscribedChannels[y],
|
||||
Currency: o.FormatExchangeCurrency(enabledCurrencies[i],
|
||||
asset.PerpetualSwap),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
subscriptions = append(subscriptions,
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsSwapAccount,
|
||||
},
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsSwapPosition,
|
||||
},
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: okGroupWsSwapOrder,
|
||||
})
|
||||
}
|
||||
case asset.Index:
|
||||
for i := range enabledCurrencies {
|
||||
for y := range defaultIndexSubscribedChannels {
|
||||
subscriptions = append(subscriptions,
|
||||
wshandler.WebsocketChannelSubscription{
|
||||
Channel: defaultIndexSubscribedChannels[y],
|
||||
Currency: o.FormatExchangeCurrency(enabledCurrencies[i], asset.Index),
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
o.Websocket.DataHandler <- errors.New("unhandled asset type")
|
||||
}
|
||||
}
|
||||
|
||||
o.Websocket.SubscribeToChannels(subscriptions)
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (o *OKGroup) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
c := channelToSubscribe.Currency.String()
|
||||
request := WebsocketEventRequest{
|
||||
Operation: "subscribe",
|
||||
Arguments: []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.String())},
|
||||
Arguments: []string{channelToSubscribe.Channel + delimiterColon + c},
|
||||
}
|
||||
if strings.EqualFold(channelToSubscribe.Channel, okGroupWsSpotAccount) {
|
||||
request.Arguments = []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.Base.String())}
|
||||
request.Arguments = []string{channelToSubscribe.Channel +
|
||||
delimiterColon +
|
||||
channelToSubscribe.Currency.Base.String()}
|
||||
}
|
||||
|
||||
return o.WebsocketConn.SendMessage(request)
|
||||
@@ -618,7 +832,9 @@ func (o *OKGroup) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscri
|
||||
func (o *OKGroup) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
request := WebsocketEventRequest{
|
||||
Operation: "unsubscribe",
|
||||
Arguments: []string{fmt.Sprintf("%v:%v", channelToSubscribe.Channel, channelToSubscribe.Currency.String())},
|
||||
Arguments: []string{channelToSubscribe.Channel +
|
||||
delimiterColon +
|
||||
channelToSubscribe.Currency.String()},
|
||||
}
|
||||
return o.WebsocketConn.SendMessage(request)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package okgroup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -79,62 +80,93 @@ func (o *OKGroup) FetchOrderbook(p currency.Pair, assetType asset.Item) (resp or
|
||||
}
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (o *OKGroup) UpdateOrderbook(p currency.Pair, assetType asset.Item) (resp orderbook.Base, err error) {
|
||||
orderbookNew, err := o.GetSpotOrderBook(GetSpotOrderBookRequest{
|
||||
InstrumentID: o.FormatExchangeCurrency(p, assetType).String(),
|
||||
})
|
||||
func (o *OKGroup) UpdateOrderbook(p currency.Pair, a asset.Item) (orderbook.Base, error) {
|
||||
var resp orderbook.Base
|
||||
if a == asset.Index {
|
||||
return resp, errors.New("no orderbooks for index")
|
||||
}
|
||||
|
||||
orderbookNew, err := o.GetOrderBook(GetOrderBookRequest{
|
||||
InstrumentID: o.FormatExchangeCurrency(p, a).String(),
|
||||
}, a)
|
||||
if err != nil {
|
||||
return
|
||||
return resp, err
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Bids {
|
||||
amount, convErr := strconv.ParseFloat(orderbookNew.Bids[x][1], 64)
|
||||
if convErr != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Could not convert %v to float64",
|
||||
orderbookNew.Bids[x][1])
|
||||
return resp, err
|
||||
}
|
||||
price, convErr := strconv.ParseFloat(orderbookNew.Bids[x][0], 64)
|
||||
if convErr != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Could not convert %v to float64",
|
||||
orderbookNew.Bids[x][0])
|
||||
return resp, err
|
||||
}
|
||||
|
||||
var liquidationOrders, orderCount int64
|
||||
// Contract specific variables
|
||||
if len(orderbookNew.Bids[x]) == 4 {
|
||||
liquidationOrders, convErr = strconv.ParseInt(orderbookNew.Bids[x][2], 10, 64)
|
||||
if convErr != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
orderCount, convErr = strconv.ParseInt(orderbookNew.Bids[x][3], 10, 64)
|
||||
if convErr != nil {
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
resp.Bids = append(resp.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
LiquidationOrders: liquidationOrders,
|
||||
OrderCount: orderCount,
|
||||
})
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Asks {
|
||||
amount, convErr := strconv.ParseFloat(orderbookNew.Asks[x][1], 64)
|
||||
if convErr != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Could not convert %v to float64",
|
||||
orderbookNew.Asks[x][1])
|
||||
return resp, err
|
||||
}
|
||||
price, convErr := strconv.ParseFloat(orderbookNew.Asks[x][0], 64)
|
||||
if convErr != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Could not convert %v to float64",
|
||||
orderbookNew.Asks[x][0])
|
||||
return resp, err
|
||||
}
|
||||
|
||||
var liquidationOrders, orderCount int64
|
||||
// Contract specific variables
|
||||
if len(orderbookNew.Asks[x]) == 4 {
|
||||
liquidationOrders, convErr = strconv.ParseInt(orderbookNew.Asks[x][2], 10, 64)
|
||||
if convErr != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
orderCount, convErr = strconv.ParseInt(orderbookNew.Asks[x][3], 10, 64)
|
||||
if convErr != nil {
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
resp.Asks = append(resp.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
LiquidationOrders: liquidationOrders,
|
||||
OrderCount: orderCount,
|
||||
})
|
||||
}
|
||||
|
||||
resp.Pair = p
|
||||
resp.AssetType = assetType
|
||||
resp.AssetType = a
|
||||
resp.ExchangeName = o.Name
|
||||
|
||||
err = resp.Process()
|
||||
if err != nil {
|
||||
return
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return orderbook.Get(o.Name, p, assetType)
|
||||
return orderbook.Get(o.Name, p, a)
|
||||
}
|
||||
|
||||
// GetAccountInfo retrieves balances for all enabled currencies
|
||||
|
||||
@@ -117,7 +117,17 @@ func (s *Service) SetNewData(b *Base) error {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] = &Book{b: b,
|
||||
// Below instigates orderbook item separation so we can ensure, in the event
|
||||
// of a simultaneous update via websocket/rest/fix, we don't affect package
|
||||
// scoped orderbook data which could result in a potential panic
|
||||
cpyBook := *b
|
||||
cpyBook.Bids = make([]Item, len(b.Bids))
|
||||
copy(cpyBook.Bids, b.Bids)
|
||||
cpyBook.Asks = make([]Item, len(b.Asks))
|
||||
copy(cpyBook.Asks, b.Asks)
|
||||
|
||||
s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] = &Book{
|
||||
b: &cpyBook,
|
||||
Main: singleID,
|
||||
Assoc: ids}
|
||||
return nil
|
||||
|
||||
@@ -50,6 +50,10 @@ type Item struct {
|
||||
Amount float64
|
||||
Price float64
|
||||
ID int64
|
||||
|
||||
// Contract variables
|
||||
LiquidationOrders int64
|
||||
OrderCount int64
|
||||
}
|
||||
|
||||
// Base holds the fields for the orderbook base
|
||||
|
||||
@@ -256,7 +256,7 @@ func TestSubmitOrder(t *testing.T) {
|
||||
|
||||
var orderSubmission = &order.Submit{
|
||||
Pair: currency.Pair{
|
||||
Delimiter: "_",
|
||||
Delimiter: delimiterUnderscore,
|
||||
Base: currency.BTC,
|
||||
Quote: currency.LTC,
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
wsTickerDataID = 1002
|
||||
ws24HourExchangeVolumeID = 1003
|
||||
wsHeartbeat = 1010
|
||||
delimiterUnderscore = "_"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -107,10 +108,15 @@ func (p *Poloniex) WsHandleData() {
|
||||
if len(data) == 2 && chanID != wsHeartbeat {
|
||||
if checkSubscriptionSuccess(data) {
|
||||
if p.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "poloniex websocket subscribed to channel successfully. %d", chanID)
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s websocket subscribed to channel successfully. %d",
|
||||
p.Name,
|
||||
chanID)
|
||||
}
|
||||
} else {
|
||||
p.Websocket.DataHandler <- fmt.Errorf("poloniex websocket subscription to channel failed. %d", chanID)
|
||||
p.Websocket.DataHandler <- fmt.Errorf("%s websocket subscription to channel failed. %d",
|
||||
p.Name,
|
||||
chanID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -135,17 +141,20 @@ func (p *Poloniex) WsHandleData() {
|
||||
dataL3map := dataL3[1].(map[string]interface{})
|
||||
currencyPair, ok := dataL3map["currencyPair"].(string)
|
||||
if !ok {
|
||||
p.Websocket.DataHandler <- errors.New("poloniex.go error - could not find currency pair in map")
|
||||
p.Websocket.DataHandler <- fmt.Errorf("%s websocket could not find currency pair in map",
|
||||
p.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
orderbookData, ok := dataL3map["orderBook"].([]interface{})
|
||||
if !ok {
|
||||
p.Websocket.DataHandler <- errors.New("poloniex.go error - could not find orderbook data in map")
|
||||
p.Websocket.DataHandler <- fmt.Errorf("%s websocket could not find orderbook data in map",
|
||||
p.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
err := p.WsProcessOrderbookSnapshot(orderbookData, currencyPair)
|
||||
err = p.WsProcessOrderbookSnapshot(orderbookData,
|
||||
currencyPair)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
continue
|
||||
@@ -158,7 +167,9 @@ func (p *Poloniex) WsHandleData() {
|
||||
}
|
||||
case "o":
|
||||
currencyPair := currencyIDMap[chanID]
|
||||
err := p.WsProcessOrderbookUpdate(int64(data[1].(float64)), dataL3, currencyPair)
|
||||
err = p.WsProcessOrderbookUpdate(int64(data[1].(float64)),
|
||||
dataL3,
|
||||
currencyPair)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
continue
|
||||
@@ -180,8 +191,16 @@ func (p *Poloniex) WsHandleData() {
|
||||
side = "sell"
|
||||
}
|
||||
trade.Side = side
|
||||
trade.Volume, _ = strconv.ParseFloat(dataL3[3].(string), 64)
|
||||
trade.Price, _ = strconv.ParseFloat(dataL3[4].(string), 64)
|
||||
trade.Volume, err = strconv.ParseFloat(dataL3[3].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
trade.Price, err = strconv.ParseFloat(dataL3[4].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
trade.Timestamp = int64(dataL3[5].(float64))
|
||||
|
||||
p.Websocket.DataHandler <- wshandler.TradeData{
|
||||
@@ -203,16 +222,60 @@ func (p *Poloniex) WsHandleData() {
|
||||
func (p *Poloniex) wsHandleTickerData(data []interface{}) {
|
||||
tickerData := data[2].([]interface{})
|
||||
var t WsTicker
|
||||
currencyPair := currencyIDMap[int(tickerData[0].(float64))]
|
||||
t.LastPrice, _ = strconv.ParseFloat(tickerData[1].(string), 64)
|
||||
t.LowestAsk, _ = strconv.ParseFloat(tickerData[2].(string), 64)
|
||||
t.HighestBid, _ = strconv.ParseFloat(tickerData[3].(string), 64)
|
||||
t.PercentageChange, _ = strconv.ParseFloat(tickerData[4].(string), 64)
|
||||
t.BaseCurrencyVolume24H, _ = strconv.ParseFloat(tickerData[5].(string), 64)
|
||||
t.QuoteCurrencyVolume24H, _ = strconv.ParseFloat(tickerData[6].(string), 64)
|
||||
currencyPair := currency.NewPairDelimiter(currencyIDMap[int(tickerData[0].(float64))], delimiterUnderscore)
|
||||
if !p.GetEnabledPairs(asset.Spot).Contains(currencyPair, true) {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
t.LastPrice, err = strconv.ParseFloat(tickerData[1].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
t.LowestAsk, err = strconv.ParseFloat(tickerData[2].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
t.HighestBid, err = strconv.ParseFloat(tickerData[3].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
t.PercentageChange, err = strconv.ParseFloat(tickerData[4].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
t.BaseCurrencyVolume24H, err = strconv.ParseFloat(tickerData[5].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
t.QuoteCurrencyVolume24H, err = strconv.ParseFloat(tickerData[6].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
t.IsFrozen = tickerData[7].(float64) == 1
|
||||
t.HighestTradeIn24H, _ = strconv.ParseFloat(tickerData[8].(string), 64)
|
||||
t.LowestTradePrice24H, _ = strconv.ParseFloat(tickerData[9].(string), 64)
|
||||
t.HighestTradeIn24H, err = strconv.ParseFloat(tickerData[8].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
t.LowestTradePrice24H, err = strconv.ParseFloat(tickerData[9].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
p.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Exchange: p.Name,
|
||||
@@ -225,7 +288,7 @@ func (p *Poloniex) wsHandleTickerData(data []interface{}) {
|
||||
Last: t.LastPrice,
|
||||
Timestamp: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairDelimiter(currencyPair, "_"),
|
||||
Pair: currencyPair,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +297,12 @@ func (p *Poloniex) wsHandleAccountData(accountData [][]interface{}) {
|
||||
for i := range accountData {
|
||||
switch accountData[i][0].(string) {
|
||||
case "b":
|
||||
amount, _ := strconv.ParseFloat(accountData[i][3].(string), 64)
|
||||
amount, err := strconv.ParseFloat(accountData[i][3].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
response := WsAccountBalanceUpdateResponse{
|
||||
currencyID: accountData[i][1].(float64),
|
||||
wallet: accountData[i][2].(string),
|
||||
@@ -242,9 +310,23 @@ func (p *Poloniex) wsHandleAccountData(accountData [][]interface{}) {
|
||||
}
|
||||
p.Websocket.DataHandler <- response
|
||||
case "n":
|
||||
timeParse, _ := time.Parse("2006-01-02 15:04:05", accountData[i][6].(string))
|
||||
rate, _ := strconv.ParseFloat(accountData[i][4].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(accountData[i][5].(string), 64)
|
||||
timeParse, err := time.Parse("2006-01-02 15:04:05", accountData[i][6].(string))
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
rate, err := strconv.ParseFloat(accountData[i][4].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(accountData[i][5].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
response := WsNewLimitOrderResponse{
|
||||
currencyID: accountData[i][1].(float64),
|
||||
@@ -262,11 +344,35 @@ func (p *Poloniex) wsHandleAccountData(accountData [][]interface{}) {
|
||||
}
|
||||
p.Websocket.DataHandler <- response
|
||||
case "t":
|
||||
timeParse, _ := time.Parse("2006-01-02 15:04:05", accountData[i][8].(string))
|
||||
rate, _ := strconv.ParseFloat(accountData[i][2].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(accountData[i][3].(string), 64)
|
||||
feeMultiplier, _ := strconv.ParseFloat(accountData[i][4].(string), 64)
|
||||
totalFee, _ := strconv.ParseFloat(accountData[i][7].(string), 64)
|
||||
timeParse, err := time.Parse("2006-01-02 15:04:05", accountData[i][8].(string))
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
rate, err := strconv.ParseFloat(accountData[i][2].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(accountData[i][3].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
feeMultiplier, err := strconv.ParseFloat(accountData[i][4].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
totalFee, err := strconv.ParseFloat(accountData[i][7].(string), 64)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
response := WsTradeNotificationResponse{
|
||||
TradeID: accountData[i][1].(float64),
|
||||
@@ -336,7 +442,6 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e
|
||||
|
||||
// WsProcessOrderbookUpdate processes new orderbook updates
|
||||
func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []interface{}, symbol string) error {
|
||||
sideCheck := target[1].(float64)
|
||||
cP := currency.NewPairFromString(symbol)
|
||||
price, err := strconv.ParseFloat(target[2].(string), 64)
|
||||
if err != nil {
|
||||
@@ -347,11 +452,11 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []inter
|
||||
return err
|
||||
}
|
||||
update := &wsorderbook.WebsocketOrderbookUpdate{
|
||||
CurrencyPair: cP,
|
||||
AssetType: asset.Spot,
|
||||
UpdateID: sequenceNumber,
|
||||
Pair: cP,
|
||||
Asset: asset.Spot,
|
||||
UpdateID: sequenceNumber,
|
||||
}
|
||||
if sideCheck == 0 {
|
||||
if target[1].(float64) == 1 {
|
||||
update.Bids = []orderbook.Item{{Price: price, Amount: volume}}
|
||||
} else {
|
||||
update.Asks = []orderbook.Item{{Price: price, Amount: volume}}
|
||||
@@ -374,7 +479,7 @@ func (p *Poloniex) GenerateDefaultSubscriptions() {
|
||||
|
||||
enabledCurrencies := p.GetEnabledPairs(asset.Spot)
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = "_"
|
||||
enabledCurrencies[j].Delimiter = delimiterUnderscore
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: "orderbook",
|
||||
Currency: enabledCurrencies[j],
|
||||
|
||||
@@ -58,11 +58,11 @@ func (p *Poloniex) SetDefaults() {
|
||||
},
|
||||
UseGlobalFormat: true,
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Delimiter: "_",
|
||||
Delimiter: delimiterUnderscore,
|
||||
Uppercase: true,
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Delimiter: "_",
|
||||
Delimiter: delimiterUnderscore,
|
||||
Uppercase: true,
|
||||
},
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) error {
|
||||
|
||||
p.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
|
||||
@@ -277,8 +277,13 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re
|
||||
reader = resp.Body
|
||||
|
||||
default:
|
||||
log.Warnf(log.ExchangeSys, "%s request response content type differs from JSON; received %v [path: %s]\n",
|
||||
r.Name, resp.Header.Get("Content-Type"), path)
|
||||
if verbose {
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s request response content type differs from JSON; received %v [path: %s]\n",
|
||||
r.Name,
|
||||
resp.Header.Get("Content-Type"),
|
||||
path)
|
||||
}
|
||||
reader = resp.Body
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,27 +25,28 @@ func (w *WebsocketOrderbookLocal) Setup(obBufferLimit int, bufferEnabled, sortBu
|
||||
// Volume == 0; deletion at price target
|
||||
// Price target not found; append of price target
|
||||
// Price target found; amend volume of price target
|
||||
func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpdate) error {
|
||||
if (orderbookUpdate.Bids == nil && orderbookUpdate.Asks == nil) ||
|
||||
(len(orderbookUpdate.Bids) == 0 && len(orderbookUpdate.Asks) == 0) {
|
||||
return fmt.Errorf("%v cannot have bids and ask targets both nil", w.exchangeName)
|
||||
func (w *WebsocketOrderbookLocal) Update(u *WebsocketOrderbookUpdate) error {
|
||||
if (u.Bids == nil && u.Asks == nil) || (len(u.Bids) == 0 && len(u.Asks) == 0) {
|
||||
return fmt.Errorf("%v cannot have bids and ask targets both nil",
|
||||
w.exchangeName)
|
||||
}
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
obLookup, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]
|
||||
obLookup, ok := w.ob[u.Pair][u.Asset]
|
||||
if !ok {
|
||||
return fmt.Errorf("ob.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s",
|
||||
w.exchangeName,
|
||||
orderbookUpdate.CurrencyPair.String(),
|
||||
orderbookUpdate.AssetType)
|
||||
u.Pair,
|
||||
u.Asset)
|
||||
}
|
||||
|
||||
if w.bufferEnabled {
|
||||
overBufferLimit := w.processBufferUpdate(obLookup, orderbookUpdate)
|
||||
overBufferLimit := w.processBufferUpdate(obLookup, u)
|
||||
if !overBufferLimit {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
w.processObUpdate(obLookup, orderbookUpdate)
|
||||
w.processObUpdate(obLookup, u)
|
||||
}
|
||||
err := obLookup.Process()
|
||||
if err != nil {
|
||||
@@ -53,23 +54,23 @@ func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpda
|
||||
}
|
||||
if w.bufferEnabled {
|
||||
// Reset the buffer
|
||||
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = nil
|
||||
w.buffer[u.Pair][u.Asset] = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) bool {
|
||||
func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, u *WebsocketOrderbookUpdate) bool {
|
||||
if w.buffer == nil {
|
||||
w.buffer = make(map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate)
|
||||
}
|
||||
if w.buffer[orderbookUpdate.CurrencyPair] == nil {
|
||||
w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]*WebsocketOrderbookUpdate)
|
||||
if w.buffer[u.Pair] == nil {
|
||||
w.buffer[u.Pair] = make(map[asset.Item][]*WebsocketOrderbookUpdate)
|
||||
}
|
||||
bufferLookup := w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]
|
||||
bufferLookup := w.buffer[u.Pair][u.Asset]
|
||||
if len(bufferLookup) <= w.obBufferLimit {
|
||||
bufferLookup = append(bufferLookup, orderbookUpdate)
|
||||
bufferLookup = append(bufferLookup, u)
|
||||
if len(bufferLookup) < w.obBufferLimit {
|
||||
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup
|
||||
w.buffer[u.Pair][u.Asset] = bufferLookup
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -88,61 +89,59 @@ func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, orderbo
|
||||
for i := 0; i < len(bufferLookup); i++ {
|
||||
w.processObUpdate(o, bufferLookup[i])
|
||||
}
|
||||
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup
|
||||
w.buffer[u.Pair][u.Asset] = bufferLookup
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) {
|
||||
func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, u *WebsocketOrderbookUpdate) {
|
||||
if w.updateEntriesByID {
|
||||
w.updateByIDAndAction(o, orderbookUpdate)
|
||||
w.updateByIDAndAction(o, u)
|
||||
} else {
|
||||
w.updateAsksByPrice(o, orderbookUpdate)
|
||||
w.updateBidsByPrice(o, orderbookUpdate)
|
||||
w.updateAsksByPrice(o, u)
|
||||
w.updateBidsByPrice(o, u)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) {
|
||||
for j := 0; j < len(base.Asks); j++ {
|
||||
found := false
|
||||
for k := 0; k < len(o.Asks); k++ {
|
||||
if o.Asks[k].Price == base.Asks[j].Price {
|
||||
found = true
|
||||
if base.Asks[j].Amount == 0 {
|
||||
o.Asks = append(o.Asks[:k],
|
||||
o.Asks[k+1:]...)
|
||||
break
|
||||
func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, u *WebsocketOrderbookUpdate) {
|
||||
updates:
|
||||
for j := range u.Asks {
|
||||
for k := range o.Asks {
|
||||
if o.Asks[k].Price == u.Asks[j].Price {
|
||||
if u.Asks[j].Amount <= 0 {
|
||||
o.Asks = append(o.Asks[:k], o.Asks[k+1:]...)
|
||||
continue updates
|
||||
}
|
||||
o.Asks[k].Amount = base.Asks[j].Amount
|
||||
break
|
||||
o.Asks[k].Amount = u.Asks[j].Amount
|
||||
continue updates
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
o.Asks = append(o.Asks, base.Asks[j])
|
||||
if u.Asks[j].Amount == 0 {
|
||||
continue
|
||||
}
|
||||
o.Asks = append(o.Asks, u.Asks[j])
|
||||
}
|
||||
sort.Slice(o.Asks, func(i, j int) bool {
|
||||
return o.Asks[i].Price < o.Asks[j].Price
|
||||
})
|
||||
}
|
||||
|
||||
func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) {
|
||||
for j := 0; j < len(base.Bids); j++ {
|
||||
found := false
|
||||
for k := 0; k < len(o.Bids); k++ {
|
||||
if o.Bids[k].Price == base.Bids[j].Price {
|
||||
found = true
|
||||
if base.Bids[j].Amount == 0 {
|
||||
o.Bids = append(o.Bids[:k],
|
||||
o.Bids[k+1:]...)
|
||||
break
|
||||
func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, u *WebsocketOrderbookUpdate) {
|
||||
updates:
|
||||
for j := range u.Bids {
|
||||
for k := range o.Bids {
|
||||
if o.Bids[k].Price == u.Bids[j].Price {
|
||||
if u.Bids[j].Amount <= 0 {
|
||||
o.Bids = append(o.Bids[:k], o.Bids[k+1:]...)
|
||||
continue updates
|
||||
}
|
||||
o.Bids[k].Amount = base.Bids[j].Amount
|
||||
break
|
||||
o.Bids[k].Amount = u.Bids[j].Amount
|
||||
continue updates
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
o.Bids = append(o.Bids, base.Bids[j])
|
||||
if u.Bids[j].Amount == 0 {
|
||||
continue
|
||||
}
|
||||
o.Bids = append(o.Bids, u.Bids[j])
|
||||
}
|
||||
sort.Slice(o.Bids, func(i, j int) bool {
|
||||
return o.Bids[i].Price > o.Bids[j].Price
|
||||
@@ -151,49 +150,52 @@ func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, base *Web
|
||||
|
||||
// updateByIDAndAction will receive an action to execute against the orderbook
|
||||
// it will then match by IDs instead of price to perform the action
|
||||
func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) {
|
||||
switch orderbookUpdate.Action {
|
||||
func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, u *WebsocketOrderbookUpdate) {
|
||||
switch u.Action {
|
||||
case "update":
|
||||
for _, target := range orderbookUpdate.Bids {
|
||||
for i := range o.Bids {
|
||||
if o.Bids[i].ID == target.ID {
|
||||
o.Bids[i].Amount = target.Amount
|
||||
for x := range u.Bids {
|
||||
for y := range o.Bids {
|
||||
if o.Bids[y].ID == u.Bids[x].ID {
|
||||
o.Bids[y].Amount = u.Bids[x].Amount
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, target := range orderbookUpdate.Asks {
|
||||
for i := range o.Asks {
|
||||
if o.Asks[i].ID == target.ID {
|
||||
o.Asks[i].Amount = target.Amount
|
||||
for x := range u.Asks {
|
||||
for y := range o.Asks {
|
||||
if o.Asks[y].ID == u.Asks[x].ID {
|
||||
o.Asks[y].Amount = u.Asks[x].Amount
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "delete":
|
||||
for _, target := range orderbookUpdate.Bids {
|
||||
for i := 0; i < len(o.Bids); i++ {
|
||||
if o.Bids[i].ID == target.ID {
|
||||
o.Bids = append(o.Bids[:i],
|
||||
o.Bids[i+1:]...)
|
||||
i--
|
||||
for x := range u.Bids {
|
||||
for y := 0; y < len(o.Bids); y++ {
|
||||
if o.Bids[y].ID == u.Bids[x].ID {
|
||||
o.Bids = append(o.Bids[:y], o.Bids[y+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, target := range orderbookUpdate.Asks {
|
||||
for i := 0; i < len(o.Asks); i++ {
|
||||
if o.Asks[i].ID == target.ID {
|
||||
o.Asks = append(o.Asks[:i],
|
||||
o.Asks[i+1:]...)
|
||||
i--
|
||||
for x := range u.Asks {
|
||||
for y := 0; y < len(o.Asks); y++ {
|
||||
if o.Asks[y].ID == u.Asks[x].ID {
|
||||
o.Asks = append(o.Asks[:y], o.Asks[y+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "insert":
|
||||
o.Bids = append(o.Bids, orderbookUpdate.Bids...)
|
||||
o.Asks = append(o.Asks, orderbookUpdate.Asks...)
|
||||
o.Bids = append(o.Bids, u.Bids...)
|
||||
sort.Slice(o.Bids, func(i, j int) bool {
|
||||
return o.Bids[i].Price > o.Bids[j].Price
|
||||
})
|
||||
|
||||
o.Asks = append(o.Asks, u.Asks...)
|
||||
sort.Slice(o.Asks, func(i, j int) bool {
|
||||
return o.Asks[i].Price < o.Asks[j].Price
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,22 +227,17 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) err
|
||||
if w.ob[newOrderbook.Pair] == nil {
|
||||
w.ob[newOrderbook.Pair] = make(map[asset.Item]*orderbook.Base)
|
||||
}
|
||||
fullObLookup := w.ob[newOrderbook.Pair][newOrderbook.AssetType]
|
||||
if fullObLookup != nil &&
|
||||
(len(fullObLookup.Asks) > 0 ||
|
||||
len(fullObLookup.Bids) > 0) {
|
||||
fullObLookup = newOrderbook
|
||||
return newOrderbook.Process()
|
||||
}
|
||||
|
||||
w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook
|
||||
return newOrderbook.Process()
|
||||
}
|
||||
|
||||
// GetOrderbook use sparingly. Modifying anything here will ruin hash calculation and cause problems
|
||||
func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, assetType asset.Item) *orderbook.Base {
|
||||
// GetOrderbook use sparingly. Modifying anything here will ruin hash
|
||||
// calculation and cause problems
|
||||
func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, a asset.Item) *orderbook.Base {
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
return w.ob[p][assetType]
|
||||
return w.ob[p][a]
|
||||
}
|
||||
|
||||
// FlushCache flushes w.ob data to be garbage collected and refreshed when a
|
||||
|
||||
@@ -20,14 +20,15 @@ var itemArray = [][]orderbook.Item{
|
||||
{{Price: 5000, Amount: 1, ID: 5}},
|
||||
}
|
||||
|
||||
var cp = currency.NewPairFromString("BTCUSD")
|
||||
|
||||
const (
|
||||
exchangeName = "exchangeTest"
|
||||
)
|
||||
|
||||
func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, bids []orderbook.Item, err error) {
|
||||
func createSnapshot() (obl *WebsocketOrderbookLocal, asks, bids []orderbook.Item, err error) {
|
||||
var snapShot1 orderbook.Base
|
||||
snapShot1.ExchangeName = exchangeName
|
||||
curr = currency.NewPairFromString("BTCUSD")
|
||||
asks = []orderbook.Item{
|
||||
{Price: 4000, Amount: 1, ID: 6},
|
||||
}
|
||||
@@ -37,7 +38,7 @@ func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, b
|
||||
snapShot1.Asks = asks
|
||||
snapShot1.Bids = bids
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
snapShot1.Pair = cp
|
||||
obl = &WebsocketOrderbookLocal{exchangeName: exchangeName}
|
||||
err = obl.LoadSnapshot(&snapShot1)
|
||||
return
|
||||
@@ -52,7 +53,7 @@ func bidAskGenerator() []orderbook.Item {
|
||||
price = 1
|
||||
}
|
||||
response = append(response, orderbook.Item{
|
||||
Amount: float64(rand.Intn(1)),
|
||||
Amount: float64(rand.Intn(10)),
|
||||
Price: price,
|
||||
ID: int64(i),
|
||||
})
|
||||
@@ -61,7 +62,7 @@ func bidAskGenerator() []orderbook.Item {
|
||||
}
|
||||
|
||||
func BenchmarkUpdateBidsByPrice(b *testing.B) {
|
||||
ob, curr, _, _, err := createSnapshot()
|
||||
ob, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
@@ -69,18 +70,18 @@ func BenchmarkUpdateBidsByPrice(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bidAsks := bidAskGenerator()
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bidAsks,
|
||||
Asks: bidAsks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bidAsks,
|
||||
Asks: bidAsks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
}
|
||||
ob.updateBidsByPrice(ob.ob[curr][asset.Spot], update)
|
||||
ob.updateBidsByPrice(ob.ob[cp][asset.Spot], update)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUpdateAsksByPrice(b *testing.B) {
|
||||
ob, curr, _, _, err := createSnapshot()
|
||||
ob, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
@@ -88,25 +89,24 @@ func BenchmarkUpdateAsksByPrice(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bidAsks := bidAskGenerator()
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bidAsks,
|
||||
Asks: bidAsks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bidAsks,
|
||||
Asks: bidAsks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
}
|
||||
ob.updateAsksByPrice(ob.ob[curr][asset.Spot], update)
|
||||
ob.updateAsksByPrice(ob.ob[cp][asset.Spot], update)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkBufferPerformance demonstrates buffer more performant than multi
|
||||
// process calls
|
||||
func BenchmarkBufferPerformance(b *testing.B) {
|
||||
obl, curr, asks, bids, err := createSnapshot()
|
||||
obl, asks, bids, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
obl.bufferEnabled = true
|
||||
cp := currency.NewPairFromString("BTCUSD")
|
||||
// This is to ensure we do not send in zero orderbook info to our main book
|
||||
// in orderbook.go, orderbooks should not be zero even after an update.
|
||||
dummyItem := orderbook.Item{
|
||||
@@ -116,11 +116,11 @@ func BenchmarkBufferPerformance(b *testing.B) {
|
||||
}
|
||||
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -136,13 +136,12 @@ func BenchmarkBufferPerformance(b *testing.B) {
|
||||
|
||||
// BenchmarkBufferSortingPerformance benchmark
|
||||
func BenchmarkBufferSortingPerformance(b *testing.B) {
|
||||
obl, curr, asks, bids, err := createSnapshot()
|
||||
obl, asks, bids, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
obl.bufferEnabled = true
|
||||
obl.sortBuffer = true
|
||||
cp := currency.NewPairFromString("BTCUSD")
|
||||
// This is to ensure we do not send in zero orderbook info to our main book
|
||||
// in orderbook.go, orderbooks should not be zero even after an update.
|
||||
dummyItem := orderbook.Item{
|
||||
@@ -152,11 +151,11 @@ func BenchmarkBufferSortingPerformance(b *testing.B) {
|
||||
}
|
||||
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -172,14 +171,13 @@ func BenchmarkBufferSortingPerformance(b *testing.B) {
|
||||
|
||||
// BenchmarkBufferSortingPerformance benchmark
|
||||
func BenchmarkBufferSortingByIDPerformance(b *testing.B) {
|
||||
obl, curr, asks, bids, err := createSnapshot()
|
||||
obl, asks, bids, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
obl.bufferEnabled = true
|
||||
obl.sortBuffer = true
|
||||
obl.sortBufferByUpdateIDs = true
|
||||
cp := currency.NewPairFromString("BTCUSD")
|
||||
// This is to ensure we do not send in zero orderbook info to our main book
|
||||
// in orderbook.go, orderbooks should not be zero even after an update.
|
||||
dummyItem := orderbook.Item{
|
||||
@@ -189,11 +187,11 @@ func BenchmarkBufferSortingByIDPerformance(b *testing.B) {
|
||||
}
|
||||
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -210,11 +208,10 @@ func BenchmarkBufferSortingByIDPerformance(b *testing.B) {
|
||||
// BenchmarkNoBufferPerformance demonstrates orderbook process less performant
|
||||
// than buffer
|
||||
func BenchmarkNoBufferPerformance(b *testing.B) {
|
||||
obl, curr, asks, bids, err := createSnapshot()
|
||||
obl, asks, bids, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
cp := currency.NewPairFromString("BTCUSD")
|
||||
// This is to ensure we do not send in zero orderbook info to our main book
|
||||
// in orderbook.go, orderbooks should not be zero even after an update.
|
||||
dummyItem := orderbook.Item{
|
||||
@@ -224,11 +221,11 @@ func BenchmarkNoBufferPerformance(b *testing.B) {
|
||||
}
|
||||
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -243,41 +240,41 @@ func BenchmarkNoBufferPerformance(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestUpdates(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
obl.updateAsksByPrice(obl.ob[curr][asset.Spot], &WebsocketOrderbookUpdate{
|
||||
Bids: itemArray[5],
|
||||
Asks: itemArray[5],
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{
|
||||
Bids: itemArray[5],
|
||||
Asks: itemArray[5],
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
obl.updateAsksByPrice(obl.ob[curr][asset.Spot], &WebsocketOrderbookUpdate{
|
||||
Bids: itemArray[0],
|
||||
Asks: itemArray[0],
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{
|
||||
Bids: itemArray[0],
|
||||
Asks: itemArray[0],
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
|
||||
if len(obl.ob[cp][asset.Spot].Asks) != 3 {
|
||||
t.Error("Did not update")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHittingTheBuffer logic test
|
||||
func TestHittingTheBuffer(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -287,30 +284,30 @@ func TestHittingTheBuffer(t *testing.T) {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
|
||||
t.Log(obl.ob[curr][asset.Spot])
|
||||
if len(obl.ob[cp][asset.Spot].Asks) != 3 {
|
||||
t.Log(obl.ob[cp][asset.Spot])
|
||||
t.Errorf("expected 3 entries, received: %v",
|
||||
len(obl.ob[curr][asset.Spot].Asks))
|
||||
len(obl.ob[cp][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 3 {
|
||||
if len(obl.ob[cp][asset.Spot].Bids) != 3 {
|
||||
t.Errorf("expected 3 entries, received: %v",
|
||||
len(obl.ob[curr][asset.Spot].Bids))
|
||||
len(obl.ob[cp][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestInsertWithIDs logic test
|
||||
func TestInsertWithIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -321,30 +318,30 @@ func TestInsertWithIDs(t *testing.T) {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Action: "insert",
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
Action: "insert",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 6 {
|
||||
if len(obl.ob[cp][asset.Spot].Asks) != 6 {
|
||||
t.Errorf("expected 6 entries, received: %v",
|
||||
len(obl.ob[curr][asset.Spot].Asks))
|
||||
len(obl.ob[cp][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 6 {
|
||||
if len(obl.ob[cp][asset.Spot].Bids) != 6 {
|
||||
t.Errorf("expected 6 entries, received: %v",
|
||||
len(obl.ob[curr][asset.Spot].Bids))
|
||||
len(obl.ob[cp][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSortIDs logic test
|
||||
func TestSortIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -356,34 +353,33 @@ func TestSortIDs(t *testing.T) {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateID: int64(i),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateID: int64(i),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
|
||||
if len(obl.ob[cp][asset.Spot].Asks) != 3 {
|
||||
t.Errorf("expected 3 entries, received: %v",
|
||||
len(obl.ob[curr][asset.Spot].Asks))
|
||||
len(obl.ob[cp][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 3 {
|
||||
if len(obl.ob[cp][asset.Spot].Bids) != 3 {
|
||||
t.Errorf("expected 3 entries, received: %v",
|
||||
len(obl.ob[curr][asset.Spot].Bids))
|
||||
len(obl.ob[cp][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteWithIDs logic test
|
||||
func TestDeleteWithIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cp := currency.NewPairFromString("BTCUSD")
|
||||
// This is to ensure we do not send in zero orderbook info to our main book
|
||||
// in orderbook.go, orderbooks should not be zero even after an update.
|
||||
dummyItem := orderbook.Item{
|
||||
@@ -392,35 +388,41 @@ func TestDeleteWithIDs(t *testing.T) {
|
||||
ID: 1337,
|
||||
}
|
||||
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
|
||||
obl.ob[cp][asset.Spot].Asks = append(obl.ob[cp][asset.Spot].Asks,
|
||||
itemArray[2][0])
|
||||
obl.ob[cp][asset.Spot].Asks = append(obl.ob[cp][asset.Spot].Asks,
|
||||
itemArray[1][0])
|
||||
|
||||
obl.updateEntriesByID = true
|
||||
for i := 0; i < len(itemArray); i++ {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Action: "delete",
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
Action: "delete",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 0 {
|
||||
|
||||
if len(obl.ob[cp][asset.Spot].Asks) != 0 {
|
||||
t.Errorf("expected 0 entries, received: %v",
|
||||
len(obl.ob[curr][asset.Spot].Asks))
|
||||
len(obl.ob[cp][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 1 {
|
||||
if len(obl.ob[cp][asset.Spot].Bids) != 1 {
|
||||
t.Errorf("expected 1 entries, received: %v",
|
||||
len(obl.ob[curr][asset.Spot].Bids))
|
||||
len(obl.ob[cp][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateWithIDs logic test
|
||||
func TestUpdateWithIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -429,35 +431,38 @@ func TestUpdateWithIDs(t *testing.T) {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Action: "update",
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
Action: "update",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 1 {
|
||||
t.Log(obl.ob[curr][asset.Spot])
|
||||
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
|
||||
if len(obl.ob[cp][asset.Spot].Asks) != 1 {
|
||||
t.Log(obl.ob[cp][asset.Spot])
|
||||
t.Errorf("expected 1 entries, received: %v",
|
||||
len(obl.ob[cp][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 1 {
|
||||
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
|
||||
if len(obl.ob[cp][asset.Spot].Bids) != 1 {
|
||||
t.Errorf("expected 1 entries, received: %v",
|
||||
len(obl.ob[cp][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestOutOfOrderIDs logic test
|
||||
func TestOutOfOrderIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outOFOrderIDs := []int64{2, 1, 5, 3, 4, 6, 7}
|
||||
if itemArray[0][0].Price != 1000 {
|
||||
t.Errorf("expected sorted price to be 3000, received: %v", itemArray[1][0].Price)
|
||||
t.Errorf("expected sorted price to be 3000, received: %v",
|
||||
itemArray[1][0].Price)
|
||||
}
|
||||
obl.bufferEnabled = true
|
||||
obl.sortBuffer = true
|
||||
@@ -465,18 +470,19 @@ func TestOutOfOrderIDs(t *testing.T) {
|
||||
for i := 0; i < len(itemArray); i++ {
|
||||
asks := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateID: outOFOrderIDs[i],
|
||||
AssetType: asset.Spot,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateID: outOFOrderIDs[i],
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Index 1 since index 0 is price 7000
|
||||
if obl.ob[curr][asset.Spot].Asks[1].Price != 2000 {
|
||||
t.Errorf("expected sorted price to be 3000, received: %v", obl.ob[curr][asset.Spot].Asks[1].Price)
|
||||
if obl.ob[cp][asset.Spot].Asks[1].Price != 2000 {
|
||||
t.Errorf("expected sorted price to be 3000, received: %v",
|
||||
obl.ob[cp][asset.Spot].Asks[1].Price)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +490,6 @@ func TestOutOfOrderIDs(t *testing.T) {
|
||||
func TestRunUpdateWithoutSnapshot(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
asks := []orderbook.Item{
|
||||
{Price: 4000, Amount: 1, ID: 8},
|
||||
}
|
||||
@@ -495,14 +500,14 @@ func TestRunUpdateWithoutSnapshot(t *testing.T) {
|
||||
snapShot1.Asks = asks
|
||||
snapShot1.Bids = bids
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
snapShot1.Pair = cp
|
||||
obl.exchangeName = exchangeName
|
||||
err := obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error running update with no snapshot loaded")
|
||||
@@ -516,18 +521,17 @@ func TestRunUpdateWithoutSnapshot(t *testing.T) {
|
||||
func TestRunUpdateWithoutAnyUpdates(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
snapShot1.Asks = []orderbook.Item{}
|
||||
snapShot1.Bids = []orderbook.Item{}
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
snapShot1.Pair = cp
|
||||
obl.exchangeName = exchangeName
|
||||
err := obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: snapShot1.Asks,
|
||||
Asks: snapShot1.Bids,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Bids: snapShot1.Asks,
|
||||
Asks: snapShot1.Bids,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error running update with no snapshot loaded")
|
||||
@@ -542,11 +546,10 @@ func TestRunUpdateWithoutAnyUpdates(t *testing.T) {
|
||||
func TestRunSnapshotWithNoData(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
snapShot1.Asks = []orderbook.Item{}
|
||||
snapShot1.Bids = []orderbook.Item{}
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
snapShot1.Pair = cp
|
||||
snapShot1.ExchangeName = "test"
|
||||
obl.exchangeName = "test"
|
||||
err := obl.LoadSnapshot(&snapShot1)
|
||||
@@ -563,7 +566,6 @@ func TestLoadSnapshot(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
snapShot1.ExchangeName = "SnapshotWithOverride"
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
asks := []orderbook.Item{
|
||||
{Price: 4000, Amount: 1, ID: 8},
|
||||
}
|
||||
@@ -573,7 +575,7 @@ func TestLoadSnapshot(t *testing.T) {
|
||||
snapShot1.Asks = asks
|
||||
snapShot1.Bids = bids
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
snapShot1.Pair = cp
|
||||
err := obl.LoadSnapshot(&snapShot1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -582,15 +584,15 @@ func TestLoadSnapshot(t *testing.T) {
|
||||
|
||||
// TestFlushCache logic test
|
||||
func TestFlushCache(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if obl.ob[curr][asset.Spot] == nil {
|
||||
if obl.ob[cp][asset.Spot] == nil {
|
||||
t.Error("expected ob to have ask entries")
|
||||
}
|
||||
obl.FlushCache()
|
||||
if obl.ob[curr][asset.Spot] != nil {
|
||||
if obl.ob[cp][asset.Spot] != nil {
|
||||
t.Error("expected ob be flushed")
|
||||
}
|
||||
|
||||
@@ -632,7 +634,7 @@ func TestInsertingSnapShots(t *testing.T) {
|
||||
snapShot1.Asks = asks
|
||||
snapShot1.Bids = bids
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = currency.NewPairFromString("BTCUSD")
|
||||
snapShot1.Pair = cp
|
||||
err := obl.LoadSnapshot(&snapShot1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -714,23 +716,29 @@ func TestInsertingSnapShots(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0] != snapShot1.Asks[0] {
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot1.Asks[0], obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0])
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v",
|
||||
snapShot1.Asks[0],
|
||||
obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0])
|
||||
}
|
||||
if obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0] != snapShot2.Asks[0] {
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot2.Asks[0], obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0])
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v",
|
||||
snapShot2.Asks[0],
|
||||
obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0])
|
||||
}
|
||||
if obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0] != snapShot3.Asks[0] {
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot3.Asks[0], obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0])
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v",
|
||||
snapShot3.Asks[0],
|
||||
obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderbook(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ob := obl.GetOrderbook(curr, asset.Spot)
|
||||
if obl.ob[curr][asset.Spot] != ob {
|
||||
ob := obl.GetOrderbook(cp, asset.Spot)
|
||||
if obl.ob[cp][asset.Spot] != ob {
|
||||
t.Error("Failed to get orderbook")
|
||||
}
|
||||
}
|
||||
@@ -738,7 +746,35 @@ func TestGetOrderbook(t *testing.T) {
|
||||
func TestSetup(t *testing.T) {
|
||||
w := WebsocketOrderbookLocal{}
|
||||
w.Setup(1, true, true, true, true, "hi")
|
||||
if w.obBufferLimit != 1 || !w.bufferEnabled || !w.sortBuffer || !w.sortBufferByUpdateIDs || !w.updateEntriesByID || w.exchangeName != "hi" {
|
||||
if w.obBufferLimit != 1 ||
|
||||
!w.bufferEnabled ||
|
||||
!w.sortBuffer ||
|
||||
!w.sortBufferByUpdateIDs ||
|
||||
!w.updateEntriesByID ||
|
||||
w.exchangeName != "hi" {
|
||||
t.Errorf("Setup incorrectly loaded %s", w.exchangeName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureMultipleUpdatesViaPrice(t *testing.T) {
|
||||
obl, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
asks := bidAskGenerator()
|
||||
obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{
|
||||
Bids: asks,
|
||||
Asks: asks,
|
||||
Pair: cp,
|
||||
UpdateTime: time.Now(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(obl.ob[cp][asset.Spot].Asks) <= 3 {
|
||||
t.Errorf("Insufficient updates")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ type WebsocketOrderbookLocal struct {
|
||||
|
||||
// WebsocketOrderbookUpdate stores orderbook updates and dictates what features to use when processing
|
||||
type WebsocketOrderbookUpdate struct {
|
||||
UpdateID int64 // Used when no time is provided
|
||||
UpdateTime time.Time
|
||||
AssetType asset.Item
|
||||
Action string // Used in conjunction with UpdateEntriesByID
|
||||
Bids []orderbook.Item
|
||||
Asks []orderbook.Item
|
||||
CurrencyPair currency.Pair
|
||||
UpdateID int64 // Used when no time is provided
|
||||
UpdateTime time.Time
|
||||
Asset asset.Item
|
||||
Action string // Used in conjunction with UpdateEntriesByID
|
||||
Bids []orderbook.Item
|
||||
Asks []orderbook.Item
|
||||
Pair currency.Pair
|
||||
}
|
||||
|
||||
@@ -287,8 +287,8 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
OrderType: order.AnyType,
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.LTC,
|
||||
currency.BTC)},
|
||||
Currencies: []currency.Pair{currency.NewPair(currency.XRP,
|
||||
currency.USDT)},
|
||||
}
|
||||
|
||||
_, err := z.GetActiveOrders(&getOrdersRequest)
|
||||
@@ -327,6 +327,7 @@ func areTestAPIKeysSet() bool {
|
||||
func TestSubmitOrder(t *testing.T) {
|
||||
z.SetDefaults()
|
||||
TestSetup(t)
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders {
|
||||
t.Skip(fmt.Sprintf("ApiKey: %s. Can place orders: %v",
|
||||
z.API.Credentials.Key,
|
||||
@@ -336,8 +337,8 @@ func TestSubmitOrder(t *testing.T) {
|
||||
var orderSubmission = &order.Submit{
|
||||
Pair: currency.Pair{
|
||||
Delimiter: "_",
|
||||
Base: currency.QTUM,
|
||||
Quote: currency.USD,
|
||||
Base: currency.XRP,
|
||||
Quote: currency.USDT,
|
||||
},
|
||||
OrderSide: order.Buy,
|
||||
OrderType: order.Limit,
|
||||
@@ -361,7 +362,7 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
currencyPair := currency.NewPair(currency.XRP, currency.USDT)
|
||||
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
@@ -387,7 +388,7 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
|
||||
currencyPair := currency.NewPair(currency.LTC, currency.BTC)
|
||||
currencyPair := currency.NewPair(currency.XRP, currency.USDT)
|
||||
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
|
||||
@@ -104,7 +104,7 @@ func (z *ZB) WsHandleData() {
|
||||
Last: ticker.Data.Last,
|
||||
Bid: ticker.Data.Buy,
|
||||
Ask: ticker.Data.Sell,
|
||||
Timestamp: time.Unix(0, ticker.Date),
|
||||
Timestamp: time.Unix(0, ticker.Date*int64(time.Millisecond)),
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromString(cPair[0]),
|
||||
}
|
||||
@@ -119,19 +119,17 @@ func (z *ZB) WsHandleData() {
|
||||
|
||||
var asks []orderbook.Item
|
||||
for i := range depth.Asks {
|
||||
ask := depth.Asks[i].([]interface{})
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: ask[1].(float64),
|
||||
Price: ask[0].(float64),
|
||||
Amount: depth.Asks[i][1].(float64),
|
||||
Price: depth.Asks[i][0].(float64),
|
||||
})
|
||||
}
|
||||
|
||||
var bids []orderbook.Item
|
||||
for i := range depth.Bids {
|
||||
bid := depth.Bids[i].([]interface{})
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: bid[1].(float64),
|
||||
Price: bid[0].(float64),
|
||||
Amount: depth.Bids[i][1].(float64),
|
||||
Price: depth.Bids[i][0].(float64),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -172,7 +170,7 @@ func (z *ZB) WsHandleData() {
|
||||
channelInfo := strings.Split(result.Channel, "_")
|
||||
cPair := currency.NewPairFromString(channelInfo[0])
|
||||
z.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(0, t.Date),
|
||||
Timestamp: time.Unix(0, t.Date*int64(time.Millisecond)),
|
||||
CurrencyPair: cPair,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: z.GetName(),
|
||||
|
||||
@@ -43,20 +43,20 @@ type WsTicker struct {
|
||||
|
||||
// WsDepth defines websocket orderbook data
|
||||
type WsDepth struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Asks []interface{} `json:"asks"`
|
||||
Bids []interface{} `json:"bids"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Asks [][]interface{} `json:"asks"`
|
||||
Bids [][]interface{} `json:"bids"`
|
||||
}
|
||||
|
||||
// WsTrades defines websocket trade data
|
||||
type WsTrades struct {
|
||||
Data []struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
TID int64 `json:"tid"`
|
||||
Date int64 `json:"date"`
|
||||
Type string `json:"type"`
|
||||
TradeType string `json:"trade_type"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
TID interface{} `json:"tid"`
|
||||
Date int64 `json:"date"`
|
||||
Type string `json:"type"`
|
||||
TradeType string `json:"trade_type"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package zb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -221,15 +221,16 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Price,
|
||||
return tickerPrice, err
|
||||
}
|
||||
|
||||
for _, x := range z.GetEnabledPairs(assetType) {
|
||||
enabledPairs := z.GetEnabledPairs(assetType)
|
||||
for x := range enabledPairs {
|
||||
// We can't use either pair format here, so format it to lower-
|
||||
// case and without any delimiter
|
||||
curr := x.Format("", false).String()
|
||||
curr := enabledPairs[x].Format("", false).String()
|
||||
if _, ok := result[curr]; !ok {
|
||||
continue
|
||||
}
|
||||
var tp ticker.Price
|
||||
tp.Pair = x
|
||||
tp.Pair = enabledPairs[x]
|
||||
tp.High = result[curr].High
|
||||
tp.Last = result[curr].Last
|
||||
tp.Ask = result[curr].Sell
|
||||
@@ -276,12 +277,14 @@ func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.B
|
||||
|
||||
for x := range orderbookNew.Bids {
|
||||
data := orderbookNew.Bids[x]
|
||||
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]})
|
||||
orderBook.Bids = append(orderBook.Bids,
|
||||
orderbook.Item{Amount: data[1], Price: data[0]})
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Asks {
|
||||
data := orderbookNew.Asks[x]
|
||||
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]})
|
||||
orderBook.Asks = append(orderBook.Asks,
|
||||
orderbook.Item{Amount: data[1], Price: data[0]})
|
||||
}
|
||||
|
||||
orderBook.Pair = p
|
||||
@@ -366,7 +369,7 @@ func (z *ZB) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
}
|
||||
response, err := z.SpotNewOrder(params)
|
||||
if response > 0 {
|
||||
submitOrderResponse.OrderID = fmt.Sprintf("%v", response)
|
||||
submitOrderResponse.OrderID = strconv.FormatInt(response, 10)
|
||||
}
|
||||
if err == nil {
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
@@ -398,13 +401,13 @@ func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) {
|
||||
var allOpenOrders []Order
|
||||
enabledPairs := z.GetEnabledPairs(asset.Spot)
|
||||
for x := range enabledPairs {
|
||||
// Limiting to 10 pages
|
||||
for pageNumber := int64(0); pageNumber < 11; pageNumber++ {
|
||||
fCurr := z.FormatExchangeCurrency(enabledPairs[x], asset.Spot).String()
|
||||
openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(fCurr,
|
||||
pageNumber,
|
||||
10)
|
||||
fPair := z.FormatExchangeCurrency(enabledPairs[x], asset.Spot).String()
|
||||
for y := int64(1); ; y++ {
|
||||
openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(fPair, y, 10)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "3001") {
|
||||
break
|
||||
}
|
||||
return cancelAllOrdersResponse, err
|
||||
}
|
||||
|
||||
@@ -413,6 +416,10 @@ func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) {
|
||||
}
|
||||
|
||||
allOpenOrders = append(allOpenOrders, openOrders...)
|
||||
|
||||
if len(openOrders) != 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,20 +488,25 @@ func (z *ZB) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
func (z *ZB) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
var allOrders []Order
|
||||
for x := range req.Currencies {
|
||||
// Limiting to 10 pages
|
||||
for pageNumber := int64(0); pageNumber < 11; pageNumber++ {
|
||||
fCurr := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String()
|
||||
resp, err := z.GetUnfinishedOrdersIgnoreTradeType(fCurr,
|
||||
pageNumber,
|
||||
10)
|
||||
for i := int64(1); ; i++ {
|
||||
fPair := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String()
|
||||
resp, err := z.GetUnfinishedOrdersIgnoreTradeType(fPair, i, 10)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "3001") {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resp) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
allOrders = append(allOrders, resp...)
|
||||
|
||||
if len(resp) != 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,7 +517,7 @@ func (z *ZB) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error
|
||||
orderDate := time.Unix(int64(allOrders[i].TradeDate), 0)
|
||||
orderSide := orderSideMap[allOrders[i].Type]
|
||||
orders = append(orders, order.Detail{
|
||||
ID: fmt.Sprintf("%d", allOrders[i].ID),
|
||||
ID: strconv.FormatInt(allOrders[i].ID, 10),
|
||||
Amount: allOrders[i].TotalAmount,
|
||||
Exchange: z.Name,
|
||||
OrderDate: orderDate,
|
||||
@@ -536,10 +548,9 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error
|
||||
}
|
||||
|
||||
for x := range req.Currencies {
|
||||
// Limiting to 10 pages
|
||||
for pageNumber := int64(0); pageNumber < 11; pageNumber++ {
|
||||
fCurr := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String()
|
||||
resp, err := z.GetOrders(fCurr, pageNumber, side)
|
||||
for y := int64(1); ; y++ {
|
||||
fPair := z.FormatExchangeCurrency(req.Currencies[x], asset.Spot).String()
|
||||
resp, err := z.GetOrders(fPair, y, side)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -549,6 +560,10 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error
|
||||
}
|
||||
|
||||
allOrders = append(allOrders, resp...)
|
||||
|
||||
if len(resp) != 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +574,7 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error
|
||||
orderDate := time.Unix(int64(allOrders[i].TradeDate), 0)
|
||||
orderSide := orderSideMap[allOrders[i].Type]
|
||||
orders = append(orders, order.Detail{
|
||||
ID: fmt.Sprintf("%d", allOrders[i].ID),
|
||||
ID: strconv.FormatInt(allOrders[i].ID, 10),
|
||||
Amount: allOrders[i].TotalAmount,
|
||||
Exchange: z.Name,
|
||||
OrderDate: orderDate,
|
||||
|
||||
Reference in New Issue
Block a user