mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-18 15:10:03 +00:00
* initial concept of a nice validation tester for exchanges * adds some datahandler design * expand testing * more tests and fixes * minor end of day fix for bithumb * fixes implementation issues * more test coverage and improvements, but not sure if i should continue * fix more wrapper implementations * adds error type, more fixes * changes signature, fixes implementations * fixes more wrapper implementations * one more bit * more cleanup * WOW things work? * lintle 1/1337 * mini bump * fixes all linting * neaten * GetOrderInfo+ asset pair fixes+improvements * adds new websocket test * expand ws testing * fix bug, expand tests, improve implementation * code coverage of a lot of new codes * fixes everything * reverts accidental changes * minor fixes from reviewing code * removes Bitfinex cancelBatchOrder implementation * fixes dumb baby typo for babies * mini nit fixes * so many nits to address * addresses all the nits * Titlecase * switcheroo * removes websocket testing for now * fix appveyor, minor test fix * fixes typo, re-kindles killed kode * skip binance wrapper tests when running CI * expired context, huobi okx fixes * kodespull * fix ordering * time fix because why not * fix exmo, others * hopefully this fixes all of my life's problems * last thing today * huobi, more like hypotrophy * golangci-lint, more like mypooroldknee-splint * fix huobi times by removing them * should fix okx currency issues * blocks the application * adds last little contingency for pairs * addresses most nits and new problems * lovely fixed before seeing why okx sucks * fixes issues with okx websocket * the classic receieieivaier * lintle * adds test and fixes existing tests * expands error handling messages during setup * fixes dumb okx bugs introduced * quick fix for lint and exmo * fixes nixes * fix exmo deposit issue * lint * fixes issue with extra asset runs missing * fix surprise race * all the lint and merge fixes * fixes surprise bugs in OKx * fixes issues with times and chains * fixing all the merge stuff * merge fix * rm logs and a panic potential * lovely lint lament * an easy demonstration of scenario, but not of initial purpose * put it in the bin * Revert "put it in the bin" This reverts commit 15c6490f713233d43f10957367fcbf18e3818bdd. * re-add after immediate error popup * fix mini poor test design * okx okay * merge fixes * fixes issues discovered in lovely test * I FORGOT TO COMMIT THIS * nit fixaroonaboo * forgoetten test fix * revert old okx asset intrument work * fixes * revert problems I didnt understand. update bybit * fix merge bugs * test cleanup * further improvements * reshuffle and lint * rm redundant CI_TEST by rm the CI_TEST field that is redundant * path fix * move to its own section, dont run on 32 bit + appveyor * lint * fix lbank * address nits * let it rip * fix failing test time range * niteroo boogaloo * mod tidy, use common.SimpleTimeFormat
625 lines
17 KiB
Go
625 lines
17 KiB
Go
package bittrex
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/flate"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
)
|
|
|
|
const (
|
|
bittrexAPIWSURL = "wss://socket-v3.bittrex.com/signalr"
|
|
bittrexAPIWSNegotiationsURL = "https://socket-v3.bittrex.com/signalr"
|
|
|
|
bittrexWebsocketTimer = 13 * time.Second
|
|
wsTicker = "ticker"
|
|
wsOrderbook = "orderbook"
|
|
wsMarketSummary = "market_summary"
|
|
wsOrders = "order"
|
|
wsHeartbeat = "heartbeat"
|
|
authenticate = "Authenticate"
|
|
subscribe = "subscribe"
|
|
unsubscribe = "unsubscribe"
|
|
wsRateLimit = 50
|
|
wsMessageRateLimit = 60
|
|
)
|
|
|
|
var defaultSpotSubscribedChannels = []string{
|
|
// wsHeartbeat,
|
|
wsOrderbook,
|
|
wsTicker,
|
|
wsMarketSummary,
|
|
}
|
|
|
|
var defaultSpotSubscribedChannelsAuth = []string{
|
|
wsOrders,
|
|
}
|
|
|
|
// TickerCache holds ticker and market summary data
|
|
// in order to combine them when processing data
|
|
type TickerCache struct {
|
|
MarketSummaries map[string]*MarketSummaryData
|
|
Tickers map[string]*TickerData
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// WsConnect connects to a websocket feed
|
|
func (b *Bittrex) WsConnect() error {
|
|
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
|
return errors.New(stream.WebsocketNotEnabled)
|
|
}
|
|
|
|
var wsHandshakeData WsSignalRHandshakeData
|
|
err := b.WsSignalRHandshake(context.TODO(), &wsHandshakeData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var dialer websocket.Dialer
|
|
endpoint, err := b.API.Endpoints.GetURL(exchange.WebsocketSpot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Set("clientProtocol", "1.5")
|
|
params.Set("transport", "webSockets")
|
|
params.Set("connectionToken", wsHandshakeData.ConnectionToken)
|
|
params.Set("connectionData", "[{name:\"c3\"}]")
|
|
params.Set("tid", "10")
|
|
|
|
path := common.EncodeURLValues("/connect", params)
|
|
|
|
err = b.Websocket.SetWebsocketURL(endpoint+path, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = b.Websocket.Conn.Dial(&dialer, http.Header{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Can set up custom ping handler per websocket connection.
|
|
b.Websocket.Conn.SetupPingHandler(stream.PingHandler{
|
|
MessageType: websocket.PingMessage,
|
|
Delay: bittrexWebsocketTimer,
|
|
})
|
|
|
|
// This reader routine is called prior to initiating a subscription for
|
|
// efficient processing.
|
|
b.Websocket.Wg.Add(1)
|
|
go b.wsReadData()
|
|
|
|
b.setupOrderbookManager()
|
|
b.tickerCache = &TickerCache{
|
|
MarketSummaries: make(map[string]*MarketSummaryData),
|
|
Tickers: make(map[string]*TickerData),
|
|
}
|
|
|
|
if b.IsWebsocketAuthenticationSupported() {
|
|
err = b.WsAuth(context.TODO())
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- err
|
|
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WsSignalRHandshake requests the SignalR connection token over https
|
|
func (b *Bittrex) WsSignalRHandshake(ctx context.Context, result interface{}) error {
|
|
endpoint, err := b.API.Endpoints.GetURL(exchange.WebsocketSpotSupplementary)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := "/negotiate?connectionData=[{name:\"c3\"}]&clientProtocol=1.5"
|
|
item := &request.Item{
|
|
Method: http.MethodGet,
|
|
Path: endpoint + path,
|
|
Result: result,
|
|
Verbose: b.Verbose,
|
|
HTTPDebugging: b.HTTPDebugging,
|
|
HTTPRecording: b.HTTPRecording,
|
|
}
|
|
return b.SendPayload(ctx, request.Unset, func() (*request.Item, error) {
|
|
return item, nil
|
|
}, request.UnauthenticatedRequest)
|
|
}
|
|
|
|
// WsAuth sends an authentication message to receive auth data
|
|
// Authentications expire after 10 minutes
|
|
func (b *Bittrex) WsAuth(ctx context.Context) error {
|
|
creds, err := b.GetCredentials(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// [apiKey, timestamp in ms, random uuid, signed payload]
|
|
apiKey := creds.Key
|
|
randomContent, err := uuid.NewV4()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
|
hmac, err := crypto.GetHMAC(
|
|
crypto.HashSHA512,
|
|
[]byte(timestamp+randomContent.String()),
|
|
[]byte(creds.Secret),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signature := crypto.HexEncodeToString(hmac)
|
|
|
|
req := WsEventRequest{
|
|
Hub: "c3",
|
|
Method: authenticate,
|
|
InvocationID: b.Websocket.Conn.GenerateMessageID(false),
|
|
}
|
|
|
|
arguments := make([]string, 0)
|
|
arguments = append(arguments, apiKey, timestamp, randomContent.String(), signature)
|
|
req.Arguments = arguments
|
|
|
|
requestString, err := json.Marshal(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if b.Verbose {
|
|
log.Debugf(log.WebsocketMgr, "%s Sending JSON message - %s\n", b.Name, requestString)
|
|
}
|
|
|
|
respRaw, err := b.Websocket.Conn.SendMessageReturnResponse(req.InvocationID, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var response WsAuthResponse
|
|
err = json.Unmarshal(respRaw, &response)
|
|
if err != nil {
|
|
log.Warnf(log.WebsocketMgr, "%s - Cannot unmarshal into WsAuthResponse (%s)\n", b.Name, string(respRaw))
|
|
return err
|
|
}
|
|
if !response.Response.Success {
|
|
log.Warnf(log.WebsocketMgr, "%s - Unable to authenticate (%s)", b.Name, response.Response.ErrorCode)
|
|
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be
|
|
// handled by ManageSubscriptions()
|
|
func (b *Bittrex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
|
var subscriptions []stream.ChannelSubscription
|
|
pairs, err := b.GetEnabledPairs(asset.Spot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
channels := defaultSpotSubscribedChannels
|
|
if b.IsWebsocketAuthenticationSupported() {
|
|
channels = append(channels, defaultSpotSubscribedChannelsAuth...)
|
|
}
|
|
|
|
for i := range pairs {
|
|
pair, err := b.FormatExchangeCurrency(pairs[i], asset.Spot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for y := range channels {
|
|
var channel string
|
|
switch channels[y] {
|
|
case wsOrderbook:
|
|
channel = channels[y] + "_" + pair.String() + "_" + strconv.FormatInt(orderbookDepth, 10)
|
|
case wsTicker:
|
|
channel = channels[y] + "_" + pair.String()
|
|
case wsMarketSummary:
|
|
channel = channels[y] + "_" + pair.String()
|
|
default:
|
|
channel = channels[y]
|
|
}
|
|
subscriptions = append(subscriptions,
|
|
stream.ChannelSubscription{
|
|
Channel: channel,
|
|
Currency: pair,
|
|
Asset: asset.Spot,
|
|
})
|
|
}
|
|
}
|
|
|
|
return subscriptions, nil
|
|
}
|
|
|
|
// Subscribe sends a websocket message to receive data from the channel
|
|
func (b *Bittrex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
|
var x int
|
|
var errs error
|
|
for x = 0; x+wsMessageRateLimit < len(channelsToSubscribe); x += wsMessageRateLimit {
|
|
err := b.subscribeSlice(channelsToSubscribe[x : x+wsMessageRateLimit])
|
|
if err != nil {
|
|
errs = common.AppendError(errs, err)
|
|
}
|
|
}
|
|
err := b.subscribeSlice(channelsToSubscribe[x:])
|
|
if err != nil {
|
|
errs = common.AppendError(errs, err)
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func (b *Bittrex) subscribeSlice(channelsToSubscribe []stream.ChannelSubscription) error {
|
|
req := WsEventRequest{
|
|
Hub: "c3",
|
|
Method: subscribe,
|
|
InvocationID: b.Websocket.Conn.GenerateMessageID(false),
|
|
}
|
|
|
|
channels := make([]string, len(channelsToSubscribe))
|
|
for i := range channelsToSubscribe {
|
|
channels[i] = channelsToSubscribe[i].Channel
|
|
}
|
|
arguments := make([][]string, 0)
|
|
arguments = append(arguments, channels)
|
|
req.Arguments = arguments
|
|
|
|
requestString, err := json.Marshal(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if b.Verbose {
|
|
log.Debugf(log.WebsocketMgr, "%s - Sending JSON message - %s\n", b.Name, requestString)
|
|
}
|
|
respRaw, err := b.Websocket.Conn.SendMessageReturnResponse(req.InvocationID, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var response WsSubscriptionResponse
|
|
err = json.Unmarshal(respRaw, &response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var errs error
|
|
for i := range response.Response {
|
|
if !response.Response[i].Success {
|
|
errs = common.AppendError(errs, errors.New("unable to subscribe to "+channels[i]+" - error code "+response.Response[i].ErrorCode))
|
|
continue
|
|
}
|
|
b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
|
|
}
|
|
return errs
|
|
}
|
|
|
|
// Unsubscribe sends a websocket message to receive data from the channel
|
|
func (b *Bittrex) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
|
var x int
|
|
var errs error
|
|
for x = 0; x+wsMessageRateLimit < len(channelsToUnsubscribe); x += wsMessageRateLimit {
|
|
err := b.unsubscribeSlice(channelsToUnsubscribe[x : x+wsMessageRateLimit])
|
|
if err != nil {
|
|
errs = common.AppendError(errs, err)
|
|
}
|
|
}
|
|
err := b.unsubscribeSlice(channelsToUnsubscribe[x:])
|
|
if err != nil {
|
|
errs = common.AppendError(errs, err)
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func (b *Bittrex) unsubscribeSlice(channelsToUnsubscribe []stream.ChannelSubscription) error {
|
|
req := WsEventRequest{
|
|
Hub: "c3",
|
|
Method: unsubscribe,
|
|
InvocationID: b.Websocket.Conn.GenerateMessageID(false),
|
|
}
|
|
|
|
channels := make([]string, len(channelsToUnsubscribe))
|
|
for i := range channelsToUnsubscribe {
|
|
channels[i] = channelsToUnsubscribe[i].Channel
|
|
}
|
|
arguments := make([][]string, 0)
|
|
arguments = append(arguments, channels)
|
|
req.Arguments = arguments
|
|
|
|
requestString, err := json.Marshal(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if b.Verbose {
|
|
log.Debugf(log.WebsocketMgr, "%s - Sending JSON message - %s\n", b.Name, requestString)
|
|
}
|
|
respRaw, err := b.Websocket.Conn.SendMessageReturnResponse(req.InvocationID, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var response WsSubscriptionResponse
|
|
err = json.Unmarshal(respRaw, &response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var errs error
|
|
for i := range response.Response {
|
|
if !response.Response[i].Success {
|
|
errs = common.AppendError(errs, errors.New("unable to unsubscribe from "+channels[i]+" - error code "+response.Response[i].ErrorCode))
|
|
continue
|
|
}
|
|
b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
|
|
}
|
|
return errs
|
|
}
|
|
|
|
// wsReadData gets and passes on websocket messages for processing
|
|
func (b *Bittrex) wsReadData() {
|
|
defer b.Websocket.Wg.Done()
|
|
|
|
for {
|
|
select {
|
|
case <-b.Websocket.ShutdownC:
|
|
return
|
|
default:
|
|
resp := b.Websocket.Conn.ReadMessage()
|
|
if resp.Raw == nil {
|
|
log.Warnf(log.WebsocketMgr, "%s Received empty message\n", b.Name)
|
|
return
|
|
}
|
|
|
|
err := b.wsHandleData(resp.Raw)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Bittrex) wsDecodeMessage(encodedMessage string, v interface{}) error {
|
|
raw, err := crypto.Base64Decode(encodedMessage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
reader := flate.NewReader(bytes.NewBuffer(raw))
|
|
message, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = reader.Close(); err != nil {
|
|
log.Warnf(log.WebsocketMgr, "%s wsDecodeMessage: unable to close reader: %s",
|
|
b.Name,
|
|
err,
|
|
)
|
|
}
|
|
return json.Unmarshal(message, v)
|
|
}
|
|
|
|
func (b *Bittrex) wsHandleData(respRaw []byte) error {
|
|
var response WsEventResponse
|
|
err := json.Unmarshal(respRaw, &response)
|
|
if err != nil {
|
|
log.Warnf(log.WebsocketMgr, "%s Cannot unmarshal into eventResponse (%s)\n", b.Name, string(respRaw))
|
|
return err
|
|
}
|
|
if response.Response != nil && response.InvocationID > 0 {
|
|
if b.Websocket.Match.IncomingWithData(response.InvocationID, respRaw) {
|
|
return nil
|
|
}
|
|
return errors.New("received response to unknown request")
|
|
}
|
|
|
|
if response.Response == nil && len(response.Message) == 0 && response.C == "" {
|
|
if b.Verbose {
|
|
log.Warnf(log.WebsocketMgr, "%s Received keep-alive (%s)\n", b.Name, string(respRaw))
|
|
}
|
|
return nil
|
|
}
|
|
for i := range response.Message {
|
|
switch response.Message[i].Method {
|
|
case "orderBook":
|
|
for j := range response.Message[i].Arguments {
|
|
var orderbookUpdate OrderbookUpdateMessage
|
|
err = b.wsDecodeMessage(response.Message[i].Arguments[j], &orderbookUpdate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var init bool
|
|
init, err = b.UpdateLocalOBBuffer(&orderbookUpdate)
|
|
if err != nil {
|
|
if init {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%v - UpdateLocalCache error: %s",
|
|
b.Name,
|
|
err)
|
|
}
|
|
}
|
|
case "ticker":
|
|
for j := range response.Message[i].Arguments {
|
|
var tickerUpdate TickerData
|
|
err = b.wsDecodeMessage(response.Message[i].Arguments[j], &tickerUpdate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = b.WsProcessUpdateTicker(tickerUpdate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case "marketSummary":
|
|
for j := range response.Message[i].Arguments {
|
|
var marketSummaryUpdate MarketSummaryData
|
|
err = b.wsDecodeMessage(response.Message[i].Arguments[j], &marketSummaryUpdate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = b.WsProcessUpdateMarketSummary(&marketSummaryUpdate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case "heartbeat":
|
|
if b.Verbose {
|
|
log.Warnf(log.WebsocketMgr, "%s Received heartbeat\n", b.Name)
|
|
}
|
|
case "authenticationExpiring":
|
|
if b.Verbose {
|
|
log.Debugf(log.WebsocketMgr, "%s - Re-authenticating.\n", b.Name)
|
|
}
|
|
err = b.WsAuth(context.TODO())
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- err
|
|
b.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
|
}
|
|
case "order":
|
|
for j := range response.Message[i].Arguments {
|
|
var orderUpdate OrderUpdateMessage
|
|
err = b.wsDecodeMessage(response.Message[i].Arguments[j], &orderUpdate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = b.WsProcessUpdateOrder(&orderUpdate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WsProcessUpdateTicker processes an update on the ticker
|
|
func (b *Bittrex) WsProcessUpdateTicker(tickerData TickerData) error {
|
|
pair, err := currency.NewPairFromString(tickerData.Symbol)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tickerPrice, err := ticker.GetTicker(b.Name, pair, asset.Spot)
|
|
if err != nil {
|
|
b.tickerCache.mu.Lock()
|
|
defer b.tickerCache.mu.Unlock()
|
|
if b.tickerCache.MarketSummaries[tickerData.Symbol] != nil {
|
|
marketSummaryData := b.tickerCache.MarketSummaries[tickerData.Symbol]
|
|
tickerPrice = b.constructTicker(tickerData, marketSummaryData, pair, asset.Spot)
|
|
b.Websocket.DataHandler <- tickerPrice
|
|
return nil
|
|
}
|
|
b.tickerCache.Tickers[tickerData.Symbol] = &tickerData
|
|
return nil
|
|
}
|
|
|
|
tickerPrice.Last = tickerData.LastTradeRate
|
|
tickerPrice.Bid = tickerData.BidRate
|
|
tickerPrice.Ask = tickerData.AskRate
|
|
|
|
b.Websocket.DataHandler <- tickerPrice
|
|
|
|
return nil
|
|
}
|
|
|
|
// WsProcessUpdateMarketSummary processes an update on the ticker
|
|
func (b *Bittrex) WsProcessUpdateMarketSummary(marketSummaryData *MarketSummaryData) error {
|
|
pair, err := currency.NewPairFromString(marketSummaryData.Symbol)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tickerPrice, err := ticker.GetTicker(b.Name, pair, asset.Spot)
|
|
if err != nil {
|
|
b.tickerCache.mu.Lock()
|
|
defer b.tickerCache.mu.Unlock()
|
|
if b.tickerCache.Tickers[marketSummaryData.Symbol] != nil {
|
|
tickerData := b.tickerCache.Tickers[marketSummaryData.Symbol]
|
|
tickerPrice = b.constructTicker(*tickerData, marketSummaryData, pair, asset.Spot)
|
|
b.Websocket.DataHandler <- tickerPrice
|
|
return nil
|
|
}
|
|
b.tickerCache.MarketSummaries[marketSummaryData.Symbol] = marketSummaryData
|
|
return nil
|
|
}
|
|
|
|
tickerPrice.High = marketSummaryData.High
|
|
tickerPrice.Low = marketSummaryData.Low
|
|
tickerPrice.Volume = marketSummaryData.Volume
|
|
tickerPrice.QuoteVolume = marketSummaryData.QuoteVolume
|
|
tickerPrice.LastUpdated = marketSummaryData.UpdatedAt
|
|
|
|
b.Websocket.DataHandler <- tickerPrice
|
|
|
|
return nil
|
|
}
|
|
|
|
// WsProcessUpdateOrder processes an update on the open orders
|
|
func (b *Bittrex) WsProcessUpdateOrder(data *OrderUpdateMessage) error {
|
|
orderType, err := order.StringToOrderType(data.Delta.Type)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: b.Name,
|
|
OrderID: data.Delta.ID,
|
|
Err: err,
|
|
}
|
|
}
|
|
orderSide, err := order.StringToOrderSide(data.Delta.Direction)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: b.Name,
|
|
OrderID: data.Delta.ID,
|
|
Err: err,
|
|
}
|
|
}
|
|
orderStatus, err := order.StringToOrderStatus(data.Delta.Status)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: b.Name,
|
|
OrderID: data.Delta.ID,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
pair, err := currency.NewPairFromString(data.Delta.MarketSymbol)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: b.Name,
|
|
OrderID: data.Delta.ID,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
b.Websocket.DataHandler <- &order.Detail{
|
|
ImmediateOrCancel: data.Delta.TimeInForce == string(ImmediateOrCancel),
|
|
FillOrKill: data.Delta.TimeInForce == string(GoodTilCancelled),
|
|
PostOnly: data.Delta.TimeInForce == string(PostOnlyGoodTilCancelled),
|
|
Price: data.Delta.Limit,
|
|
Amount: data.Delta.Quantity,
|
|
RemainingAmount: data.Delta.Quantity - data.Delta.FillQuantity,
|
|
ExecutedAmount: data.Delta.FillQuantity,
|
|
Exchange: b.Name,
|
|
OrderID: data.Delta.ID,
|
|
Type: orderType,
|
|
Side: orderSide,
|
|
Status: orderStatus,
|
|
AssetType: asset.Spot,
|
|
Date: data.Delta.CreatedAt,
|
|
Pair: pair,
|
|
}
|
|
return nil
|
|
}
|