diff --git a/README.md b/README.md index 27f32c9..127b0f3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Go Bitvavo -[![Go Report Card](https://goreportcard.com/badge/github.com/larscom/go-bitvavo)](https://goreportcard.com/report/github.com/larscom/go-bitvavo) +[![Go Report Card](https://goreportcard.com/badge/github.com/larscom/go-bitvavo/v2)](https://goreportcard.com/report/github.com/larscom/go-bitvavo/v2) [![Go Reference](https://pkg.go.dev/badge/github.com/larscom/go-bitvavo.svg)](https://pkg.go.dev/github.com/larscom/go-bitvavo) > Go **thread safe** client library for Bitvavo v2 (https://docs.bitvavo.com) @@ -10,7 +10,7 @@ Go Bitvavo is a **thread safe** client written in GO to interact with the Bitvav ## 📒 Features - [x] WebSockets - -- Read Only + -- Read only - [ ] REST (_soon_) -- Read / Write - [ ] ... diff --git a/account.go b/account.go index 5f5ecf7..a160b45 100644 --- a/account.go +++ b/account.go @@ -145,13 +145,13 @@ func (o *OrderEvent) UnmarshalJSON(data []byte) error { Status: status, Side: side, OrderType: orderType, - Amount: util.IfOrElse(len(amount) > 0, func() float64 { return util.MustFloat64(amount) }, ZERO), - AmountRemaining: util.IfOrElse(len(amountRemaining) > 0, func() float64 { return util.MustFloat64(amountRemaining) }, ZERO), - Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, ZERO), - OnHold: util.IfOrElse(len(onHold) > 0, func() float64 { return util.MustFloat64(onHold) }, ZERO), + Amount: util.IfOrElse(len(amount) > 0, func() float64 { return util.MustFloat64(amount) }, zerof), + AmountRemaining: util.IfOrElse(len(amountRemaining) > 0, func() float64 { return util.MustFloat64(amountRemaining) }, zerof), + Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, zerof), + OnHold: util.IfOrElse(len(onHold) > 0, func() float64 { return util.MustFloat64(onHold) }, zerof), OnHoldCurrency: onHoldCurrency, - TriggerPrice: util.IfOrElse(len(triggerPrice) > 0, func() float64 { return util.MustFloat64(triggerPrice) }, ZERO), - TriggerAmount: util.IfOrElse(len(triggerAmount) > 0, func() float64 { return util.MustFloat64(triggerAmount) }, ZERO), + TriggerPrice: util.IfOrElse(len(triggerPrice) > 0, func() float64 { return util.MustFloat64(triggerPrice) }, zerof), + TriggerAmount: util.IfOrElse(len(triggerAmount) > 0, func() float64 { return util.MustFloat64(triggerAmount) }, zerof), TriggerType: triggerType, TriggerReference: triggerReference, TimeInForce: timeInForce, @@ -223,11 +223,11 @@ func (f *FillEvent) UnmarshalJSON(data []byte) error { OrderId: orderId, FillId: fillId, Timestamp: int64(timestamp), - Amount: util.IfOrElse(len(amount) > 0, func() float64 { return util.MustFloat64(amount) }, ZERO), + Amount: util.IfOrElse(len(amount) > 0, func() float64 { return util.MustFloat64(amount) }, zerof), Side: side, - Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, ZERO), + Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, zerof), Taker: taker, - Fee: util.IfOrElse(len(fee) > 0, func() float64 { return util.MustFloat64(fee) }, ZERO), + Fee: util.IfOrElse(len(fee) > 0, func() float64 { return util.MustFloat64(fee) }, zerof), FeeCurrency: feeCurrency, } @@ -299,7 +299,7 @@ func (t *accountEventHandler) Subscribe(market string) (AccountSub, error) { } if err := t.withAuth(func() { - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameAccount, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameAccount, market) }); err != nil { return nil, err } @@ -317,7 +317,7 @@ func (t *accountEventHandler) Unsubscribe(market string) error { if exist { if err := t.withAuth(func() { - t.writechn <- newWebSocketMessage(ActionUnsubscribe, ChannelNameBook, market) + t.writechn <- newWebSocketMessage(actionUnsubscribe, channelNameBook, market) }); err != nil { return err } @@ -377,7 +377,7 @@ func (t *accountEventHandler) handleAuthMessage(bytes []byte) { func newWebSocketAuthMessage(apiKey string, apiSecret string, windowTimeMs uint64) WebSocketMessage { timestamp := time.Now().UnixMilli() return WebSocketMessage{ - Action: ActionAuthenticate.Value, + Action: actionAuthenticate.Value, Key: apiKey, Signature: createSignature(timestamp, apiSecret), Timestamp: timestamp, @@ -402,7 +402,7 @@ func (t *accountEventHandler) reconnect() { for sub := range t.subs.IterBuffered() { market := sub.Key if err := t.withAuth(func() { - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameAccount, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameAccount, market) }); err != nil { log.Logger().Error("Failed to reconnect the account websocket", "market", market) } diff --git a/book.go b/book.go index 1cfa100..cb50d6a 100644 --- a/book.go +++ b/book.go @@ -62,8 +62,8 @@ func (b *BookEvent) UnmarshalJSON(bytes []byte) error { size := bidEvents[i].([]any)[1].(string) bids[i] = Page{ - Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, ZERO), - Size: util.IfOrElse(len(size) > 0, func() float64 { return util.MustFloat64(size) }, ZERO), + Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, zerof), + Size: util.IfOrElse(len(size) > 0, func() float64 { return util.MustFloat64(size) }, zerof), } } @@ -74,8 +74,8 @@ func (b *BookEvent) UnmarshalJSON(bytes []byte) error { size := askEvents[i].([]any)[1].(string) asks[i] = Page{ - Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, ZERO), - Size: util.IfOrElse(len(size) > 0, func() float64 { return util.MustFloat64(size) }, ZERO), + Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, zerof), + Size: util.IfOrElse(len(size) > 0, func() float64 { return util.MustFloat64(size) }, zerof), } } @@ -107,7 +107,7 @@ func (t *bookEventHandler) Subscribe(market string, buffSize uint64) (<-chan Boo return nil, fmt.Errorf("subscription already active for market: %s", market) } - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameBook, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameBook, market) chn := make(chan BookEvent, buffSize) t.subs.Set(market, chn) @@ -119,7 +119,7 @@ func (t *bookEventHandler) Unsubscribe(market string) error { sub, exist := t.subs.Get(market) if exist { - t.writechn <- newWebSocketMessage(ActionUnsubscribe, ChannelNameBook, market) + t.writechn <- newWebSocketMessage(actionUnsubscribe, channelNameBook, market) close(sub) t.subs.Remove(market) return nil @@ -156,6 +156,6 @@ func (t *bookEventHandler) handleMessage(bytes []byte) { func (t *bookEventHandler) reconnect() { for sub := range t.subs.IterBuffered() { market := sub.Key - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameBook, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameBook, market) } } diff --git a/candles.go b/candles.go index c6cbb6b..b1278df 100644 --- a/candles.go +++ b/candles.go @@ -87,7 +87,7 @@ func newCandleWebSocketMessage(action Action, market string, interval string) We Action: action.Value, Channels: []Channel{ { - Name: ChannelNameCandles.Value, + Name: channelNameCandles.Value, Markets: []string{market}, Intervals: []string{interval}, }, @@ -102,7 +102,7 @@ func (c *candlesEventHandler) Subscribe(market string, interval string, buffSize return nil, fmt.Errorf("subscription already active for market: %s with interval: %s", market, interval) } - c.writechn <- newCandleWebSocketMessage(ActionSubscribe, market, interval) + c.writechn <- newCandleWebSocketMessage(actionSubscribe, market, interval) chn := make(chan CandlesEvent, buffSize) c.subs.Set(key, chn) @@ -115,7 +115,7 @@ func (c *candlesEventHandler) Unsubscribe(market string, interval string) error sub, exist := c.subs.Get(key) if exist { - c.writechn <- newCandleWebSocketMessage(ActionUnsubscribe, market, interval) + c.writechn <- newCandleWebSocketMessage(actionUnsubscribe, market, interval) close(sub) c.subs.Remove(key) return nil @@ -157,7 +157,7 @@ func (c *candlesEventHandler) handleMessage(bytes []byte) { func (c *candlesEventHandler) reconnect() { for sub := range c.subs.IterBuffered() { market, interval := getMapKeyValue(sub.Key) - c.writechn <- newCandleWebSocketMessage(ActionSubscribe, market, interval) + c.writechn <- newCandleWebSocketMessage(actionSubscribe, market, interval) } } diff --git a/ticker.go b/ticker.go index e5af59f..7446273 100644 --- a/ticker.go +++ b/ticker.go @@ -57,11 +57,11 @@ func (t *TickerEvent) UnmarshalJSON(data []byte) error { t.Market = market t.Ticker = Ticker{ - BestBid: util.IfOrElse(len(bestBid) > 0, func() float64 { return util.MustFloat64(bestBid) }, ZERO), - BestBidSize: util.IfOrElse(len(bestBidSize) > 0, func() float64 { return util.MustFloat64(bestBidSize) }, ZERO), - BestAsk: util.IfOrElse(len(bestAsk) > 0, func() float64 { return util.MustFloat64(bestAsk) }, ZERO), - BestAskSize: util.IfOrElse(len(bestAskSize) > 0, func() float64 { return util.MustFloat64(bestAskSize) }, ZERO), - LastPrice: util.IfOrElse(len(lastPrice) > 0, func() float64 { return util.MustFloat64(lastPrice) }, ZERO), + BestBid: util.IfOrElse(len(bestBid) > 0, func() float64 { return util.MustFloat64(bestBid) }, zerof), + BestBidSize: util.IfOrElse(len(bestBidSize) > 0, func() float64 { return util.MustFloat64(bestBidSize) }, zerof), + BestAsk: util.IfOrElse(len(bestAsk) > 0, func() float64 { return util.MustFloat64(bestAsk) }, zerof), + BestAskSize: util.IfOrElse(len(bestAskSize) > 0, func() float64 { return util.MustFloat64(bestAskSize) }, zerof), + LastPrice: util.IfOrElse(len(lastPrice) > 0, func() float64 { return util.MustFloat64(lastPrice) }, zerof), } return nil @@ -84,7 +84,7 @@ func (t *tickerEventHandler) Subscribe(market string, buffSize uint64) (<-chan T return nil, fmt.Errorf("subscription already active for market: %s", market) } - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameTicker, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameTicker, market) chn := make(chan TickerEvent, buffSize) t.subs.Set(market, chn) @@ -96,7 +96,7 @@ func (t *tickerEventHandler) Unsubscribe(market string) error { sub, exist := t.subs.Get(market) if exist { - t.writechn <- newWebSocketMessage(ActionUnsubscribe, ChannelNameTicker, market) + t.writechn <- newWebSocketMessage(actionUnsubscribe, channelNameTicker, market) close(sub) t.subs.Remove(market) return nil @@ -133,6 +133,6 @@ func (t *tickerEventHandler) handleMessage(bytes []byte) { func (t *tickerEventHandler) reconnect() { for sub := range t.subs.IterBuffered() { market := sub.Key - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameTicker, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameTicker, market) } } diff --git a/ticker24h.go b/ticker24h.go index 9f6a16c..674044b 100644 --- a/ticker24h.go +++ b/ticker24h.go @@ -101,16 +101,16 @@ func (t *Ticker24hEvent) UnmarshalJSON(bytes []byte) error { t.Event = event t.Market = market t.Ticker24h = Ticker24h{ - Open: util.IfOrElse(len(open) > 0, func() float64 { return util.MustFloat64(open) }, ZERO), - High: util.IfOrElse(len(high) > 0, func() float64 { return util.MustFloat64(high) }, ZERO), - Low: util.IfOrElse(len(low) > 0, func() float64 { return util.MustFloat64(low) }, ZERO), - Last: util.IfOrElse(len(last) > 0, func() float64 { return util.MustFloat64(last) }, ZERO), - Volume: util.IfOrElse(len(volume) > 0, func() float64 { return util.MustFloat64(volume) }, ZERO), - VolumeQuote: util.IfOrElse(len(volumeQuote) > 0, func() float64 { return util.MustFloat64(volumeQuote) }, ZERO), - Bid: util.IfOrElse(len(bid) > 0, func() float64 { return util.MustFloat64(bid) }, ZERO), - BidSize: util.IfOrElse(len(bidSize) > 0, func() float64 { return util.MustFloat64(bidSize) }, ZERO), - Ask: util.IfOrElse(len(ask) > 0, func() float64 { return util.MustFloat64(ask) }, ZERO), - AskSize: util.IfOrElse(len(askSize) > 0, func() float64 { return util.MustFloat64(askSize) }, ZERO), + Open: util.IfOrElse(len(open) > 0, func() float64 { return util.MustFloat64(open) }, zerof), + High: util.IfOrElse(len(high) > 0, func() float64 { return util.MustFloat64(high) }, zerof), + Low: util.IfOrElse(len(low) > 0, func() float64 { return util.MustFloat64(low) }, zerof), + Last: util.IfOrElse(len(last) > 0, func() float64 { return util.MustFloat64(last) }, zerof), + Volume: util.IfOrElse(len(volume) > 0, func() float64 { return util.MustFloat64(volume) }, zerof), + VolumeQuote: util.IfOrElse(len(volumeQuote) > 0, func() float64 { return util.MustFloat64(volumeQuote) }, zerof), + Bid: util.IfOrElse(len(bid) > 0, func() float64 { return util.MustFloat64(bid) }, zerof), + BidSize: util.IfOrElse(len(bidSize) > 0, func() float64 { return util.MustFloat64(bidSize) }, zerof), + Ask: util.IfOrElse(len(ask) > 0, func() float64 { return util.MustFloat64(ask) }, zerof), + AskSize: util.IfOrElse(len(askSize) > 0, func() float64 { return util.MustFloat64(askSize) }, zerof), Timestamp: int64(timestamp), StartTimestamp: int64(startTimestamp), OpenTimestamp: int64(openTimestamp), @@ -137,7 +137,7 @@ func (t *ticker24hEventHandler) Subscribe(market string, buffSize uint64) (<-cha return nil, fmt.Errorf("subscription already active for market: %s", market) } - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameTicker24h, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameTicker24h, market) chn := make(chan Ticker24hEvent, buffSize) t.subs.Set(market, chn) @@ -149,7 +149,7 @@ func (t *ticker24hEventHandler) Unsubscribe(market string) error { sub, exist := t.subs.Get(market) if exist { - t.writechn <- newWebSocketMessage(ActionUnsubscribe, ChannelNameTicker24h, market) + t.writechn <- newWebSocketMessage(actionUnsubscribe, channelNameTicker24h, market) close(sub) t.subs.Remove(market) return nil @@ -186,6 +186,6 @@ func (t *ticker24hEventHandler) handleMessage(bytes []byte) { func (t *ticker24hEventHandler) reconnect() { for sub := range t.subs.IterBuffered() { market := sub.Key - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameTicker24h, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameTicker24h, market) } } diff --git a/trades.go b/trades.go index e4c2146..c92a479 100644 --- a/trades.go +++ b/trades.go @@ -61,8 +61,8 @@ func (t *TradesEvent) UnmarshalJSON(bytes []byte) error { t.Market = market t.Trade = Trade{ Id: id, - Amount: util.IfOrElse(len(amount) > 0, func() float64 { return util.MustFloat64(amount) }, ZERO), - Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, ZERO), + Amount: util.IfOrElse(len(amount) > 0, func() float64 { return util.MustFloat64(amount) }, zerof), + Price: util.IfOrElse(len(price) > 0, func() float64 { return util.MustFloat64(price) }, zerof), Side: side, Timestamp: int64(timestamp), } @@ -87,7 +87,7 @@ func (t *tradesEventHandler) Subscribe(market string, buffSize uint64) (<-chan T return nil, fmt.Errorf("subscription already active for market: %s", market) } - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameTrades, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameTrades, market) chn := make(chan TradesEvent, buffSize) t.subs.Set(market, chn) @@ -99,7 +99,7 @@ func (t *tradesEventHandler) Unsubscribe(market string) error { sub, exist := t.subs.Get(market) if exist { - t.writechn <- newWebSocketMessage(ActionUnsubscribe, ChannelNameTrades, market) + t.writechn <- newWebSocketMessage(actionUnsubscribe, channelNameTrades, market) close(sub) t.subs.Remove(market) return nil @@ -136,6 +136,6 @@ func (t *tradesEventHandler) handleMessage(bytes []byte) { func (t *tradesEventHandler) reconnect() { for sub := range t.subs.IterBuffered() { market := sub.Key - t.writechn <- newWebSocketMessage(ActionSubscribe, ChannelNameTrades, market) + t.writechn <- newWebSocketMessage(actionSubscribe, channelNameTrades, market) } } diff --git a/types.go b/types.go index e0c5817..07ce353 100644 --- a/types.go +++ b/types.go @@ -2,51 +2,42 @@ package bitvavo import "github.com/orsinium-labs/enum" -const ZERO = float64(0) +const zerof = float64(0) type WsEvent enum.Member[string] var ( - WsEventSubscribed = WsEvent{"subscribed"} - WsEventUnsubscribed = WsEvent{"unsubscribed"} - WsEventCandles = WsEvent{"candle"} - WsEventTicker = WsEvent{"ticker"} - WsEventTicker24h = WsEvent{"ticker24h"} - WsEventTrades = WsEvent{"trade"} - WsEventBook = WsEvent{"book"} - WsEventAuth = WsEvent{"authenticate"} - WsEventAccount = WsEvent{"account"} - WsEventOrder = WsEvent{"order"} - WsEventFill = WsEvent{"fill"} + wsEventSubscribed = WsEvent{"subscribed"} + wsEventUnsubscribed = WsEvent{"unsubscribed"} + wsEventCandles = WsEvent{"candle"} + wsEventTicker = WsEvent{"ticker"} + wsEventTicker24h = WsEvent{"ticker24h"} + wsEventTrades = WsEvent{"trade"} + wsEventBook = WsEvent{"book"} + wsEventAuth = WsEvent{"authenticate"} + wsEventOrder = WsEvent{"order"} + wsEventFill = WsEvent{"fill"} ) type Action enum.Member[string] var ( - ActionSubscribe = Action{"subscribe"} - ActionUnsubscribe = Action{"unsubscribe"} - ActionAuthenticate = Action{"authenticate"} + actionSubscribe = Action{"subscribe"} + actionUnsubscribe = Action{"unsubscribe"} + actionAuthenticate = Action{"authenticate"} ) type ChannelName enum.Member[string] var ( - ChannelNameCandles = ChannelName{"candles"} - ChannelNameTicker = ChannelName{"ticker"} - ChannelNameTicker24h = ChannelName{"ticker24h"} - ChannelNameTrades = ChannelName{"trades"} - ChannelNameBook = ChannelName{"book"} - ChannelNameAccount = ChannelName{"account"} + channelNameCandles = ChannelName{"candles"} + channelNameTicker = ChannelName{"ticker"} + channelNameTicker24h = ChannelName{"ticker24h"} + channelNameTrades = ChannelName{"trades"} + channelNameBook = ChannelName{"book"} + channelNameAccount = ChannelName{"account"} ) -type SubscribedEvent struct { - // Describes the returned event over the socket. - Event string `json:"event"` - - // Subscriptions map[event][]markets - Subscriptions map[string][]string `json:"subscriptions"` -} - type AuthEvent struct { // Describes the returned event over the socket. Event string `json:"event"` diff --git a/websocket.go b/websocket.go index 8d75d3b..19a279e 100644 --- a/websocket.go +++ b/websocket.go @@ -30,7 +30,7 @@ type EventHandler[T any] interface { } type WebSocket interface { - // Close everything, including subscriptions, underlying websockets, gracefull shutdown... + // Close everything, including subscriptions, underlying websockets, graceful shutdown... Close() error // Candles event handler to handle candle events and subscriptions. @@ -278,7 +278,7 @@ func (ws *webSocket) handlError(err *WebSocketErr) { ws.logDebug("Handling incoming error", "err", err) switch err.Action { - case ActionAuthenticate.Value: + case actionAuthenticate.Value: log.Logger().Error("Failed to authenticate, wrong apiKey and/or apiSecret") default: log.Logger().Error("Could not handle error", "action", err.Action, "code", err.Code, "message", err.Message) @@ -290,27 +290,27 @@ func (ws *webSocket) handleEvent(e *BaseEvent, bytes []byte) { switch e.Event { // public - case WsEventSubscribed.Value: + case wsEventSubscribed.Value: ws.handleSubscribedEvent(bytes) - case WsEventUnsubscribed.Value: + case wsEventUnsubscribed.Value: ws.handleUnsubscribedEvent(bytes) - case WsEventCandles.Value: + case wsEventCandles.Value: ws.handleCandleEvent(bytes) - case WsEventTicker.Value: + case wsEventTicker.Value: ws.handleTickerEvent(bytes) - case WsEventTicker24h.Value: + case wsEventTicker24h.Value: ws.handleTicker24hEvent(bytes) - case WsEventTrades.Value: + case wsEventTrades.Value: ws.handleTradesEvent(bytes) - case WsEventBook.Value: + case wsEventBook.Value: ws.handleBookEvent(bytes) // authenticated - case WsEventAuth.Value: + case wsEventAuth.Value: ws.handleAuthEvent(bytes) - case WsEventOrder.Value: + case wsEventOrder.Value: ws.handleOrderEvent(bytes) - case WsEventFill.Value: + case wsEventFill.Value: ws.handleFillEvent(bytes) default: