From b7d99f741da4b873c9dcf8941666c571863d5f5d Mon Sep 17 00:00:00 2001 From: Rauno Ots Date: Mon, 30 Nov 2020 01:17:27 +0100 Subject: [PATCH] Binance: convert timestamps to time.Time (#601) * add custom Unmarshal function for JSON types * pass KlinesRequestParams as pointer, because now the object is bigger --- exchanges/binance/binance.go | 20 +- exchanges/binance/binance_test.go | 17 +- exchanges/binance/binance_types.go | 378 ++++++++++++------------- exchanges/binance/binance_websocket.go | 12 +- exchanges/binance/binance_wrapper.go | 35 +-- exchanges/binance/type_convert.go | 346 ++++++++++++++++++++++ 6 files changed, 573 insertions(+), 235 deletions(-) create mode 100644 exchanges/binance/type_convert.go diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 5c91bb70..d9909d75 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -184,10 +184,10 @@ func (b *Binance) GetAggregatedTrades(arg *AggregatedTradeRequestParams) ([]Aggr params.Set("fromId", strconv.FormatInt(arg.FromID, 10)) } if !arg.StartTime.IsZero() { - params.Set("startTime", strconv.FormatInt(convert.UnixMillis(arg.StartTime), 10)) + params.Set("startTime", timeString(arg.StartTime)) } if !arg.EndTime.IsZero() { - params.Set("endTime", strconv.FormatInt(convert.UnixMillis(arg.EndTime), 10)) + params.Set("endTime", timeString(arg.EndTime)) } // startTime and endTime are set and time between startTime and endTime is more than 1 hour @@ -231,8 +231,8 @@ func (b *Binance) batchAggregateTrades(arg *AggregatedTradeRequestParams, params // All requests returned empty return nil, nil } - params.Set("startTime", strconv.FormatInt(convert.UnixMillis(start), 10)) - params.Set("endTime", strconv.FormatInt(convert.UnixMillis(start.Add(time.Hour)), 10)) + params.Set("startTime", timeString(start)) + params.Set("endTime", timeString(start.Add(time.Hour))) path := b.API.Endpoints.URL + aggregatedTrades + "?" + params.Encode() err := b.SendHTTPRequest(path, limitDefault, &resp) if err != nil { @@ -260,7 +260,7 @@ func (b *Binance) batchAggregateTrades(arg *AggregatedTradeRequestParams, params if !arg.EndTime.IsZero() { // get index for truncating to end time lastIndex = sort.Search(len(additionalTrades), func(i int) bool { - return convert.UnixMillis(arg.EndTime) < additionalTrades[i].TimeStamp + return arg.EndTime.Before(additionalTrades[i].TimeStamp) }) } // don't include the first as the request was inclusive from last ATradeID @@ -286,7 +286,7 @@ func (b *Binance) batchAggregateTrades(arg *AggregatedTradeRequestParams, params // interval: the interval time for the data // startTime: startTime filter for kline data // endTime: endTime filter for the kline data -func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) { +func (b *Binance) GetSpotKline(arg *KlinesRequestParams) ([]CandleStick, error) { var resp interface{} var klineData []CandleStick @@ -296,11 +296,11 @@ func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) { if arg.Limit != 0 { params.Set("limit", strconv.Itoa(arg.Limit)) } - if arg.StartTime != 0 { - params.Set("startTime", strconv.FormatInt(arg.StartTime, 10)) + if !arg.StartTime.IsZero() { + params.Set("startTime", timeString(arg.StartTime)) } - if arg.EndTime != 0 { - params.Set("endTime", strconv.FormatInt(arg.EndTime, 10)) + if !arg.EndTime.IsZero() { + params.Set("endTime", timeString(arg.EndTime)) } path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, candleStick, params.Encode()) diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 812a46cd..39eb6ace 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -38,10 +38,16 @@ func setFeeBuilder() *exchange.FeeBuilder { func TestGetExchangeInfo(t *testing.T) { t.Parallel() - _, err := b.GetExchangeInfo() + info, err := b.GetExchangeInfo() if err != nil { t.Error(err) } + if mockTests { + serverTime := time.Date(2020, 4, 15, 23, 44, 38, int(861*time.Millisecond), time.UTC) + if !info.Servertime.Equal(serverTime) { + t.Errorf("Expected %v, got %v", serverTime, info.Servertime) + } + } } func TestFetchTradablePairs(t *testing.T) { @@ -104,12 +110,12 @@ func TestGetAggregatedTrades(t *testing.T) { func TestGetSpotKline(t *testing.T) { t.Parallel() - _, err := b.GetSpotKline(KlinesRequestParams{ + _, err := b.GetSpotKline(&KlinesRequestParams{ Symbol: "BTCUSDT", Interval: kline.FiveMin.Short(), Limit: 24, - StartTime: time.Unix(1577836800, 0).Unix() * 1000, - EndTime: time.Unix(1580515200, 0).Unix() * 1000, + StartTime: time.Unix(1577836800, 0), + EndTime: time.Unix(1580515200, 0), }) if err != nil { t.Error("Binance GetSpotKline() error", err) @@ -514,8 +520,7 @@ func TestGetAggregatedTradesBatched(t *testing.T) { if len(result) != tt.numExpected { t.Errorf("GetAggregatedTradesBatched() expected %v entries, got %v", tt.numExpected, len(result)) } - lastTrade := result[len(result)-1] - lastTradeTime := time.Unix(0, lastTrade.TimeStamp*int64(time.Millisecond)) + lastTradeTime := result[len(result)-1].TimeStamp if !lastTradeTime.Equal(tt.lastExpected) { t.Errorf("last trade expected %v, got %v", tt.lastExpected, lastTradeTime) } diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index 27174a15..ee762733 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -14,10 +14,10 @@ type Response struct { // ExchangeInfo holds the full exchange information type type ExchangeInfo struct { - Code int `json:"code"` - Msg string `json:"msg"` - Timezone string `json:"timezone"` - Servertime int64 `json:"serverTime"` + Code int `json:"code"` + Msg string `json:"msg"` + Timezone string `json:"timezone"` + Servertime time.Time `json:"serverTime"` RateLimits []struct { RateLimitType string `json:"rateLimitType"` Interval string `json:"interval"` @@ -97,7 +97,7 @@ type DepthUpdateParams []struct { // WebsocketDepthStream is the difference for the update depth stream type WebsocketDepthStream struct { Event string `json:"e"` - Timestamp int64 `json:"E"` + Timestamp time.Time `json:"E"` Pair string `json:"s"` FirstUpdateID int64 `json:"U"` LastUpdateID int64 `json:"u"` @@ -113,91 +113,91 @@ type RecentTradeRequestParams struct { // RecentTrade holds recent trade data type RecentTrade struct { - ID int64 `json:"id"` - Price float64 `json:"price,string"` - Quantity float64 `json:"qty,string"` - Time int64 `json:"time"` - IsBuyerMaker bool `json:"isBuyerMaker"` - IsBestMatch bool `json:"isBestMatch"` + ID int64 `json:"id"` + Price float64 `json:"price,string"` + Quantity float64 `json:"qty,string"` + Time time.Time `json:"time"` + IsBuyerMaker bool `json:"isBuyerMaker"` + IsBestMatch bool `json:"isBestMatch"` } // TradeStream holds the trade stream data type TradeStream struct { - EventType string `json:"e"` - EventTime int64 `json:"E"` - Symbol string `json:"s"` - TradeID int64 `json:"t"` - Price string `json:"p"` - Quantity string `json:"q"` - BuyerOrderID int64 `json:"b"` - SellerOrderID int64 `json:"a"` - TimeStamp int64 `json:"T"` - Maker bool `json:"m"` - BestMatchPrice bool `json:"M"` + EventType string `json:"e"` + EventTime time.Time `json:"E"` + Symbol string `json:"s"` + TradeID int64 `json:"t"` + Price string `json:"p"` + Quantity string `json:"q"` + BuyerOrderID int64 `json:"b"` + SellerOrderID int64 `json:"a"` + TimeStamp time.Time `json:"T"` + Maker bool `json:"m"` + BestMatchPrice bool `json:"M"` } // KlineStream holds the kline stream data type KlineStream struct { - EventType string `json:"e"` - EventTime int64 `json:"E"` - Symbol string `json:"s"` + EventType string `json:"e"` + EventTime time.Time `json:"E"` + Symbol string `json:"s"` Kline struct { - StartTime int64 `json:"t"` - CloseTime int64 `json:"T"` - Symbol string `json:"s"` - Interval string `json:"i"` - FirstTradeID int64 `json:"f"` - LastTradeID int64 `json:"L"` - OpenPrice float64 `json:"o,string"` - ClosePrice float64 `json:"c,string"` - HighPrice float64 `json:"h,string"` - LowPrice float64 `json:"l,string"` - Volume float64 `json:"v,string"` - NumberOfTrades int64 `json:"n"` - KlineClosed bool `json:"x"` - Quote float64 `json:"q,string"` - TakerBuyBaseAssetVolume float64 `json:"V,string"` - TakerBuyQuoteAssetVolume float64 `json:"Q,string"` + StartTime time.Time `json:"t"` + CloseTime time.Time `json:"T"` + Symbol string `json:"s"` + Interval string `json:"i"` + FirstTradeID int64 `json:"f"` + LastTradeID int64 `json:"L"` + OpenPrice float64 `json:"o,string"` + ClosePrice float64 `json:"c,string"` + HighPrice float64 `json:"h,string"` + LowPrice float64 `json:"l,string"` + Volume float64 `json:"v,string"` + NumberOfTrades int64 `json:"n"` + KlineClosed bool `json:"x"` + Quote float64 `json:"q,string"` + TakerBuyBaseAssetVolume float64 `json:"V,string"` + TakerBuyQuoteAssetVolume float64 `json:"Q,string"` } `json:"k"` } // TickerStream holds the ticker stream data type TickerStream struct { - EventType string `json:"e"` - EventTime int64 `json:"E"` - Symbol string `json:"s"` - PriceChange float64 `json:"p,string"` - PriceChangePercent float64 `json:"P,string"` - WeightedAvgPrice float64 `json:"w,string"` - ClosePrice float64 `json:"x,string"` - LastPrice float64 `json:"c,string"` - LastPriceQuantity float64 `json:"Q,string"` - BestBidPrice float64 `json:"b,string"` - BestBidQuantity float64 `json:"B,string"` - BestAskPrice float64 `json:"a,string"` - BestAskQuantity float64 `json:"A,string"` - OpenPrice float64 `json:"o,string"` - HighPrice float64 `json:"h,string"` - LowPrice float64 `json:"l,string"` - TotalTradedVolume float64 `json:"v,string"` - TotalTradedQuoteVolume float64 `json:"q,string"` - OpenTime int64 `json:"O"` - CloseTime int64 `json:"C"` - FirstTradeID int64 `json:"F"` - LastTradeID int64 `json:"L"` - NumberOfTrades int64 `json:"n"` + EventType string `json:"e"` + EventTime time.Time `json:"E"` + Symbol string `json:"s"` + PriceChange float64 `json:"p,string"` + PriceChangePercent float64 `json:"P,string"` + WeightedAvgPrice float64 `json:"w,string"` + ClosePrice float64 `json:"x,string"` + LastPrice float64 `json:"c,string"` + LastPriceQuantity float64 `json:"Q,string"` + BestBidPrice float64 `json:"b,string"` + BestBidQuantity float64 `json:"B,string"` + BestAskPrice float64 `json:"a,string"` + BestAskQuantity float64 `json:"A,string"` + OpenPrice float64 `json:"o,string"` + HighPrice float64 `json:"h,string"` + LowPrice float64 `json:"l,string"` + TotalTradedVolume float64 `json:"v,string"` + TotalTradedQuoteVolume float64 `json:"q,string"` + OpenTime time.Time `json:"O"` + CloseTime time.Time `json:"C"` + FirstTradeID int64 `json:"F"` + LastTradeID int64 `json:"L"` + NumberOfTrades int64 `json:"n"` } // HistoricalTrade holds recent trade data type HistoricalTrade struct { - Code int `json:"code"` - Msg string `json:"msg"` - ID int64 `json:"id"` - Price float64 `json:"price,string"` - Quantity float64 `json:"qty,string"` - Time int64 `json:"time"` - IsBuyerMaker bool `json:"isBuyerMaker"` - IsBestMatch bool `json:"isBestMatch"` + Code int `json:"code"` + Msg string `json:"msg"` + ID int64 `json:"id"` + Price float64 `json:"price,string"` + Quantity float64 `json:"qty,string"` + Time time.Time `json:"time"` + IsBuyerMaker bool `json:"isBuyerMaker"` + IsBestMatch bool `json:"isBestMatch"` } // AggregatedTradeRequestParams holds request params @@ -214,14 +214,14 @@ type AggregatedTradeRequestParams struct { // AggregatedTrade holds aggregated trade information type AggregatedTrade struct { - ATradeID int64 `json:"a"` - Price float64 `json:"p,string"` - Quantity float64 `json:"q,string"` - FirstTradeID int64 `json:"f"` - LastTradeID int64 `json:"l"` - TimeStamp int64 `json:"T"` - Maker bool `json:"m"` - BestMatchPrice bool `json:"M"` + ATradeID int64 `json:"a"` + Price float64 `json:"p,string"` + Quantity float64 `json:"q,string"` + FirstTradeID int64 `json:"f"` + LastTradeID int64 `json:"l"` + TimeStamp time.Time `json:"T"` + Maker bool `json:"m"` + BestMatchPrice bool `json:"M"` } // CandleStick holds kline data @@ -247,25 +247,25 @@ type AveragePrice struct { // PriceChangeStats contains statistics for the last 24 hours trade type PriceChangeStats struct { - Symbol string `json:"symbol"` - PriceChange float64 `json:"priceChange,string"` - PriceChangePercent float64 `json:"priceChangePercent,string"` - WeightedAvgPrice float64 `json:"weightedAvgPrice,string"` - PrevClosePrice float64 `json:"prevClosePrice,string"` - LastPrice float64 `json:"lastPrice,string"` - LastQty float64 `json:"lastQty,string"` - BidPrice float64 `json:"bidPrice,string"` - AskPrice float64 `json:"askPrice,string"` - OpenPrice float64 `json:"openPrice,string"` - HighPrice float64 `json:"highPrice,string"` - LowPrice float64 `json:"lowPrice,string"` - Volume float64 `json:"volume,string"` - QuoteVolume float64 `json:"quoteVolume,string"` - OpenTime int64 `json:"openTime"` - CloseTime int64 `json:"closeTime"` - FirstID int64 `json:"firstId"` - LastID int64 `json:"lastId"` - Count int64 `json:"count"` + Symbol string `json:"symbol"` + PriceChange float64 `json:"priceChange,string"` + PriceChangePercent float64 `json:"priceChangePercent,string"` + WeightedAvgPrice float64 `json:"weightedAvgPrice,string"` + PrevClosePrice float64 `json:"prevClosePrice,string"` + LastPrice float64 `json:"lastPrice,string"` + LastQty float64 `json:"lastQty,string"` + BidPrice float64 `json:"bidPrice,string"` + AskPrice float64 `json:"askPrice,string"` + OpenPrice float64 `json:"openPrice,string"` + HighPrice float64 `json:"highPrice,string"` + LowPrice float64 `json:"lowPrice,string"` + Volume float64 `json:"volume,string"` + QuoteVolume float64 `json:"quoteVolume,string"` + OpenTime time.Time `json:"openTime"` + CloseTime time.Time `json:"closeTime"` + FirstID int64 `json:"firstId"` + LastID int64 `json:"lastId"` + Count int64 `json:"count"` } // SymbolPrice holds basic symbol price @@ -307,15 +307,15 @@ type NewOrderRequest struct { // NewOrderResponse is the return structured response from the exchange type NewOrderResponse struct { - Code int `json:"code"` - Msg string `json:"msg"` - Symbol string `json:"symbol"` - OrderID int64 `json:"orderId"` - ClientOrderID string `json:"clientOrderId"` - TransactionTime int64 `json:"transactTime"` - Price float64 `json:"price,string"` - OrigQty float64 `json:"origQty,string"` - ExecutedQty float64 `json:"executedQty,string"` + Code int `json:"code"` + Msg string `json:"msg"` + Symbol string `json:"symbol"` + OrderID int64 `json:"orderId"` + ClientOrderID string `json:"clientOrderId"` + TransactionTime time.Time `json:"transactTime"` + Price float64 `json:"price,string"` + OrigQty float64 `json:"origQty,string"` + ExecutedQty float64 `json:"executedQty,string"` // The cumulative amount of the quote that has been spent (with a BUY order) or received (with a SELL order). CumulativeQuoteQty float64 `json:"cummulativeQuoteQty,string"` Status string `json:"status"` @@ -340,26 +340,26 @@ type CancelOrderResponse struct { // QueryOrderData holds query order data type QueryOrderData struct { - Code int `json:"code"` - Msg string `json:"msg"` - Symbol string `json:"symbol"` - OrderID int64 `json:"orderId"` - ClientOrderID string `json:"clientOrderId"` - Price float64 `json:"price,string"` - OrigQty float64 `json:"origQty,string"` - ExecutedQty float64 `json:"executedQty,string"` - Status string `json:"status"` - TimeInForce string `json:"timeInForce"` - Type string `json:"type"` - Side string `json:"side"` - StopPrice float64 `json:"stopPrice,string"` - IcebergQty float64 `json:"icebergQty,string"` - Time float64 `json:"time"` - IsWorking bool `json:"isWorking"` - CummulativeQuoteQty float64 `json:"cummulativeQuoteQty,string"` - OrderListID int64 `json:"orderListId"` - OrigQuoteOrderQty float64 `json:"origQuoteOrderQty,string"` - UpdateTime int64 `json:"updateTime"` + Code int `json:"code"` + Msg string `json:"msg"` + Symbol string `json:"symbol"` + OrderID int64 `json:"orderId"` + ClientOrderID string `json:"clientOrderId"` + Price float64 `json:"price,string"` + OrigQty float64 `json:"origQty,string"` + ExecutedQty float64 `json:"executedQty,string"` + Status string `json:"status"` + TimeInForce string `json:"timeInForce"` + Type string `json:"type"` + Side string `json:"side"` + StopPrice float64 `json:"stopPrice,string"` + IcebergQty float64 `json:"icebergQty,string"` + Time time.Time `json:"time"` + IsWorking bool `json:"isWorking"` + CummulativeQuoteQty float64 `json:"cummulativeQuoteQty,string"` + OrderListID int64 `json:"orderListId"` + OrigQuoteOrderQty float64 `json:"origQuoteOrderQty,string"` + UpdateTime time.Time `json:"updateTime"` } // Balance holds query order data @@ -378,7 +378,7 @@ type Account struct { CanTrade bool `json:"canTrade"` CanWithdraw bool `json:"canWithdraw"` CanDeposit bool `json:"canDeposit"` - UpdateTime int64 `json:"updateTime"` + UpdateTime time.Time `json:"updateTime"` Balances []Balance `json:"balances"` } @@ -427,8 +427,8 @@ type KlinesRequestParams struct { Symbol string // Required field; example LTCBTC, BTCUSDT Interval string // Time interval period Limit int // Default 500; max 500. - StartTime int64 - EndTime int64 + StartTime time.Time + EndTime time.Time } // WithdrawalFees the large list of predefined withdrawal fees @@ -616,16 +616,16 @@ type UserAccountStream struct { type wsAccountInfo struct { Stream string `json:"stream"` Data struct { - CanDeposit bool `json:"D"` - CanTrade bool `json:"T"` - CanWithdraw bool `json:"W"` - EventTime int64 `json:"E"` - LastUpdated int64 `json:"u"` - BuyerCommission float64 `json:"b"` - MakerCommission float64 `json:"m"` - SellerCommission float64 `json:"s"` - TakerCommission float64 `json:"t"` - EventType string `json:"e"` + CanDeposit bool `json:"D"` + CanTrade bool `json:"T"` + CanWithdraw bool `json:"W"` + EventTime time.Time `json:"E"` + LastUpdated time.Time `json:"u"` + BuyerCommission float64 `json:"b"` + MakerCommission float64 `json:"m"` + SellerCommission float64 `json:"s"` + TakerCommission float64 `json:"t"` + EventType string `json:"e"` Currencies []struct { Asset string `json:"a"` Available float64 `json:"f,string"` @@ -642,77 +642,77 @@ type wsAccountPosition struct { Available float64 `json:"f,string"` Locked float64 `json:"l,string"` } `json:"B"` - EventTime int64 `json:"E"` - LastUpdated int64 `json:"u"` - EventType string `json:"e"` + EventTime time.Time `json:"E"` + LastUpdated time.Time `json:"u"` + EventType string `json:"e"` } `json:"data"` } type wsBalanceUpdate struct { Stream string `json:"stream"` Data struct { - EventTime int64 `json:"E"` - ClearTime int64 `json:"T"` - BalanceDelta float64 `json:"d,string"` - Asset string `json:"a"` - EventType string `json:"e"` + EventTime time.Time `json:"E"` + ClearTime time.Time `json:"T"` + BalanceDelta float64 `json:"d,string"` + Asset string `json:"a"` + EventType string `json:"e"` } `json:"data"` } type wsOrderUpdate struct { Stream string `json:"stream"` Data struct { - ClientOrderID string `json:"C"` - EventTime int64 `json:"E"` - IcebergQuantity float64 `json:"F,string"` - LastExecutedPrice float64 `json:"L,string"` - CommissionAsset float64 `json:"N"` - OrderCreationTime int64 `json:"O"` - StopPrice float64 `json:"P,string"` - QuoteOrderQuantity float64 `json:"Q,string"` - Side string `json:"S"` - TransactionTime int64 `json:"T"` - OrderStatus string `json:"X"` - LastQuoteAssetTransactedQuantity float64 `json:"Y,string"` - CumulativeQuoteTransactedQuantity float64 `json:"Z,string"` - CancelledClientOrderID string `json:"c"` - EventType string `json:"e"` - TimeInForce string `json:"f"` - OrderListID int64 `json:"g"` - OrderID int64 `json:"i"` - LastExecutedQuantity float64 `json:"l,string"` - IsMaker bool `json:"m"` - Commission float64 `json:"n,string"` - OrderType string `json:"o"` - Price float64 `json:"p,string"` - Quantity float64 `json:"q,string"` - RejectionReason string `json:"r"` - Symbol string `json:"s"` - TradeID int64 `json:"t"` - IsOnOrderBook bool `json:"w"` - CurrentExecutionType string `json:"x"` - CumulativeFilledQuantity float64 `json:"z,string"` + ClientOrderID string `json:"C"` + EventTime time.Time `json:"E"` + IcebergQuantity float64 `json:"F,string"` + LastExecutedPrice float64 `json:"L,string"` + CommissionAsset float64 `json:"N"` + OrderCreationTime time.Time `json:"O"` + StopPrice float64 `json:"P,string"` + QuoteOrderQuantity float64 `json:"Q,string"` + Side string `json:"S"` + TransactionTime time.Time `json:"T"` + OrderStatus string `json:"X"` + LastQuoteAssetTransactedQuantity float64 `json:"Y,string"` + CumulativeQuoteTransactedQuantity float64 `json:"Z,string"` + CancelledClientOrderID string `json:"c"` + EventType string `json:"e"` + TimeInForce string `json:"f"` + OrderListID int64 `json:"g"` + OrderID int64 `json:"i"` + LastExecutedQuantity float64 `json:"l,string"` + IsMaker bool `json:"m"` + Commission float64 `json:"n,string"` + OrderType string `json:"o"` + Price float64 `json:"p,string"` + Quantity float64 `json:"q,string"` + RejectionReason string `json:"r"` + Symbol string `json:"s"` + TradeID int64 `json:"t"` + IsOnOrderBook bool `json:"w"` + CurrentExecutionType string `json:"x"` + CumulativeFilledQuantity float64 `json:"z,string"` } `json:"data"` } type wsListStatus struct { Stream string `json:"stream"` Data struct { - ListClientOrderID string `json:"C"` - EventTime int64 `json:"E"` - ListOrderStatus string `json:"L"` + ListClientOrderID string `json:"C"` + EventTime time.Time `json:"E"` + ListOrderStatus string `json:"L"` Orders []struct { ClientOrderID string `json:"c"` OrderID int64 `json:"i"` Symbol string `json:"s"` } `json:"O"` - TransactionTime int64 `json:"T"` - ContingencyType string `json:"c"` - EventType string `json:"e"` - OrderListID int64 `json:"g"` - ListStatusType string `json:"l"` - RejectionReason string `json:"r"` - Symbol string `json:"s"` + TransactionTime time.Time `json:"T"` + ContingencyType string `json:"c"` + EventType string `json:"e"` + OrderListID int64 `json:"g"` + ListStatusType string `json:"l"` + RejectionReason string `json:"r"` + Symbol string `json:"s"` } `json:"data"` } diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index f4c2dac1..38e40af8 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -229,7 +229,7 @@ func (b *Binance) wsHandleData(respRaw []byte) error { Side: oSide, Status: oStatus, AssetType: a, - Date: time.Unix(0, data.Data.OrderCreationTime*int64(time.Millisecond)), + Date: data.Data.OrderCreationTime, Pair: p, } case "listStatus": @@ -297,7 +297,7 @@ func (b *Binance) wsHandleData(respRaw []byte) error { return b.AddTradesToBuffer(trade.Data{ CurrencyPair: pair, - Timestamp: time.Unix(0, t.TimeStamp*int64(time.Millisecond)), + Timestamp: t.TimeStamp, Price: price, Amount: amount, Exchange: b.Name, @@ -329,7 +329,7 @@ func (b *Binance) wsHandleData(respRaw []byte) error { Bid: t.BestBidPrice, Ask: t.BestAskPrice, Last: t.LastPrice, - LastUpdated: time.Unix(0, t.EventTime*int64(time.Millisecond)), + LastUpdated: t.EventTime, AssetType: asset.Spot, Pair: pair, } @@ -349,12 +349,12 @@ func (b *Binance) wsHandleData(respRaw []byte) error { } b.Websocket.DataHandler <- stream.KlineData{ - Timestamp: time.Unix(0, kline.EventTime*int64(time.Millisecond)), + Timestamp: kline.EventTime, Pair: pair, AssetType: asset.Spot, Exchange: b.Name, - StartTime: time.Unix(0, kline.Kline.StartTime*int64(time.Millisecond)), - CloseTime: time.Unix(0, kline.Kline.CloseTime*int64(time.Millisecond)), + StartTime: kline.Kline.StartTime, + CloseTime: kline.Kline.CloseTime, Interval: kline.Kline.Interval, OpenPrice: kline.Kline.OpenPrice, ClosePrice: kline.Kline.ClosePrice, diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index cb5fce66..72379834 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -519,7 +519,7 @@ func (b *Binance) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trad AssetType: assetType, Price: tradeData[i].Price, Amount: tradeData[i].Quantity, - Timestamp: time.Unix(0, tradeData[i].Time*int64(time.Millisecond)), + Timestamp: tradeData[i].Time, }) } if b.IsSaveTradeDataEnabled() { @@ -564,7 +564,7 @@ func (a *AggregatedTrade) toTradeData(p currency.Pair, exchange string, aType as Amount: a.Quantity, Exchange: exchange, Price: a.Price, - Timestamp: time.Unix(0, a.TimeStamp*int64(time.Millisecond)), + Timestamp: a.TimeStamp, AssetType: aType, Side: order.AnySide, } @@ -718,11 +718,6 @@ func (b *Binance) GetOrderInfo(orderID string, pair currency.Pair, assetType ass return } - orderCloseDate, err := convert.TimeFromUnixTimestampFloat(float64(resp.UpdateTime)) - if err != nil { - return - } - status, err := order.StringToOrderStatus(resp.Status) if err != nil { return @@ -743,7 +738,7 @@ func (b *Binance) GetOrderInfo(orderID string, pair currency.Pair, assetType ass Pair: formattedPair, Cost: resp.CummulativeQuoteQty, AssetType: assetType, - CloseTime: orderCloseDate, + CloseTime: resp.UpdateTime, Status: status, Price: resp.Price, ExecutedAmount: resp.ExecutedQty, @@ -822,10 +817,6 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, for i := range resp { orderSide := order.Side(strings.ToUpper(resp[i].Side)) orderType := order.Type(strings.ToUpper(resp[i].Type)) - orderDate, err := convert.TimeFromUnixTimestampFloat(resp[i].Time) - if err != nil { - return nil, err - } pair, err := currency.NewPairFromString(resp[i].Symbol) if err != nil { @@ -834,7 +825,7 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, orders = append(orders, order.Detail{ Amount: resp[i].OrigQty, - Date: orderDate, + Date: resp[i].Time, Exchange: b.Name, ID: strconv.FormatInt(resp[i].OrderID, 10), Side: orderSide, @@ -879,10 +870,6 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, for i := range resp { orderSide := order.Side(strings.ToUpper(resp[i].Side)) orderType := order.Type(strings.ToUpper(resp[i].Type)) - orderDate, err := convert.TimeFromUnixTimestampFloat(resp[i].Time) - if err != nil { - return nil, err - } // New orders are covered in GetOpenOrders if resp[i].Status == "NEW" { continue @@ -895,7 +882,7 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, orders = append(orders, order.Detail{ Amount: resp[i].OrigQty, - Date: orderDate, + Date: resp[i].Time, Exchange: b.Name, ID: strconv.FormatInt(resp[i].OrderID, 10), Side: orderSide, @@ -948,8 +935,8 @@ func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, en req := KlinesRequestParams{ Interval: b.FormatExchangeKlineInterval(interval), Symbol: fpair.String(), - StartTime: start.Unix() * 1000, - EndTime: end.Unix() * 1000, + StartTime: start, + EndTime: end, Limit: int(b.Features.Enabled.Kline.ResultLimit), } @@ -960,7 +947,7 @@ func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, en Interval: interval, } - candles, err := b.GetSpotKline(req) + candles, err := b.GetSpotKline(&req) if err != nil { return kline.Item{}, err } @@ -1003,12 +990,12 @@ func (b *Binance) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, s req := KlinesRequestParams{ Interval: b.FormatExchangeKlineInterval(interval), Symbol: formattedPair.String(), - StartTime: dates[x].Start.UTC().Unix() * 1000, - EndTime: dates[x].End.UTC().Unix() * 1000, + StartTime: dates[x].Start, + EndTime: dates[x].End, Limit: int(b.Features.Enabled.Kline.ResultLimit), } - candles, err := b.GetSpotKline(req) + candles, err := b.GetSpotKline(&req) if err != nil { return kline.Item{}, err } diff --git a/exchanges/binance/type_convert.go b/exchanges/binance/type_convert.go new file mode 100644 index 00000000..74e58797 --- /dev/null +++ b/exchanges/binance/type_convert.go @@ -0,0 +1,346 @@ +package binance + +import ( + "encoding/json" + "strconv" + "time" + + "github.com/thrasher-corp/gocryptotrader/common/convert" +) + +// binanceTime provides an internal conversion helper +type binanceTime time.Time + +func (t *binanceTime) UnmarshalJSON(data []byte) error { + var timestamp int64 + if err := json.Unmarshal(data, ×tamp); err != nil { + return err + } + *t = binanceTime(time.Unix(0, timestamp*int64(time.Millisecond))) + return nil +} + +// Time returns a time.Time object +func (t binanceTime) Time() time.Time { + return time.Time(t) +} + +// timeString gets the time as Binance timestamp +func timeString(t time.Time) string { + return strconv.FormatInt(convert.UnixMillis(t), 10) +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *ExchangeInfo) UnmarshalJSON(data []byte) error { + type Alias ExchangeInfo + aux := &struct { + Servertime binanceTime `json:"serverTime"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Servertime = aux.Servertime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *AggregatedTrade) UnmarshalJSON(data []byte) error { + type Alias AggregatedTrade + aux := &struct { + TimeStamp binanceTime `json:"T"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.TimeStamp = aux.TimeStamp.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *NewOrderResponse) UnmarshalJSON(data []byte) error { + type Alias NewOrderResponse + aux := &struct { + TransactionTime binanceTime `json:"transactTime"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + // there can be an empty response, then `a` is set to nil + if aux != nil { + a.TransactionTime = aux.TransactionTime.Time() + } else { + a = nil + } + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *TradeStream) UnmarshalJSON(data []byte) error { + type Alias TradeStream + aux := &struct { + TimeStamp binanceTime `json:"T"` + EventTime binanceTime `json:"E"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.TimeStamp = aux.TimeStamp.Time() + a.EventTime = aux.EventTime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *KlineStream) UnmarshalJSON(data []byte) error { + type Alias KlineStream + aux := &struct { + EventTime binanceTime `json:"E"` + Kline struct { + StartTime binanceTime `json:"t"` + CloseTime binanceTime `json:"T"` + } `json:"k"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.EventTime = aux.EventTime.Time() + a.Kline.StartTime = aux.Kline.StartTime.Time() + a.Kline.CloseTime = aux.Kline.CloseTime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *TickerStream) UnmarshalJSON(data []byte) error { + type Alias TickerStream + aux := &struct { + EventTime binanceTime `json:"E"` + OpenTime binanceTime `json:"O"` + CloseTime binanceTime `json:"C"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.EventTime = aux.EventTime.Time() + a.OpenTime = aux.OpenTime.Time() + a.CloseTime = aux.CloseTime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *PriceChangeStats) UnmarshalJSON(data []byte) error { + type Alias PriceChangeStats + aux := &struct { + OpenTime binanceTime `json:"openTime"` + CloseTime binanceTime `json:"closeTime"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.OpenTime = aux.OpenTime.Time() + a.CloseTime = aux.CloseTime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *RecentTrade) UnmarshalJSON(data []byte) error { + type Alias RecentTrade + aux := &struct { + Time binanceTime `json:"time"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Time = aux.Time.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *HistoricalTrade) UnmarshalJSON(data []byte) error { + type Alias HistoricalTrade + aux := &struct { + Time binanceTime `json:"time"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Time = aux.Time.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *QueryOrderData) UnmarshalJSON(data []byte) error { + type Alias QueryOrderData + aux := &struct { + Time binanceTime `json:"time"` + UpdateTime binanceTime `json:"updateTime"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Time = aux.Time.Time() + a.UpdateTime = aux.UpdateTime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *Account) UnmarshalJSON(data []byte) error { + type Alias Account + aux := &struct { + UpdateTime binanceTime `json:"updateTime"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.UpdateTime = aux.UpdateTime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *WebsocketDepthStream) UnmarshalJSON(data []byte) error { + type Alias WebsocketDepthStream + aux := &struct { + Timestamp binanceTime `json:"E"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Timestamp = aux.Timestamp.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *wsAccountInfo) UnmarshalJSON(data []byte) error { + type Alias wsAccountInfo + aux := &struct { + Data struct { + EventTime binanceTime `json:"E"` + LastUpdated binanceTime `json:"u"` + } `json:"data"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Data.EventTime = aux.Data.EventTime.Time() + a.Data.LastUpdated = aux.Data.LastUpdated.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *wsAccountPosition) UnmarshalJSON(data []byte) error { + type Alias wsAccountPosition + aux := &struct { + Data struct { + EventTime binanceTime `json:"E"` + LastUpdated binanceTime `json:"u"` + } `json:"data"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Data.EventTime = aux.Data.EventTime.Time() + a.Data.LastUpdated = aux.Data.LastUpdated.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *wsBalanceUpdate) UnmarshalJSON(data []byte) error { + type Alias wsBalanceUpdate + aux := &struct { + Data struct { + EventTime binanceTime `json:"E"` + ClearTime binanceTime `json:"T"` + } `json:"data"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Data.EventTime = aux.Data.EventTime.Time() + a.Data.ClearTime = aux.Data.ClearTime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *wsOrderUpdate) UnmarshalJSON(data []byte) error { + type Alias wsOrderUpdate + aux := &struct { + Data struct { + EventTime binanceTime `json:"E"` + OrderCreationTime binanceTime `json:"O"` + TransactionTime binanceTime `json:"T"` + } `json:"data"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Data.EventTime = aux.Data.EventTime.Time() + a.Data.OrderCreationTime = aux.Data.OrderCreationTime.Time() + a.Data.TransactionTime = aux.Data.TransactionTime.Time() + return nil +} + +// UnmarshalJSON deserialises the JSON info, including the timestamp +func (a *wsListStatus) UnmarshalJSON(data []byte) error { + type Alias wsListStatus + aux := &struct { + Data struct { + EventTime binanceTime `json:"E"` + TransactionTime binanceTime `json:"T"` + } `json:"data"` + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + a.Data.EventTime = aux.Data.EventTime.Time() + a.Data.TransactionTime = aux.Data.TransactionTime.Time() + return nil +}