types/time, exchanges/kraken: Refactor spot/futures time types (#1936)

* types/time, exchanges/kraken: Refactor spot/futures time types

- Updated WSFuturesTickerData, WsFuturesTradeData, and other related structs to replace int64 timestamps with the new types.Time.
- Adjusted related test cases to accommodate changes in timestamp handling.
- Modified functions in kraken_wrapper and kraken_websocket to utilise the new Time type for better time management.
- Enhanced JSON unmarshalling in the Time type to handle various timestamp formats, including "0" and "0.0".

* refactor: Update JSON field name/types and improve perf

* types/time: revert to more precise check, just check for 'n'

* refactor: Ryan the F1 driver is so back 🏎️

* refactor: Enhance UnmarshalJSON error handling and simplify test cases

* ocd: Fix trigger
This commit is contained in:
Adrian Gallagher
2025-06-11 08:43:36 +10:00
committed by GitHub
parent 19b8957f3f
commit ce134a0a1d
7 changed files with 360 additions and 381 deletions

View File

@@ -2,8 +2,10 @@ package kraken
import (
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/types"
)
var (
@@ -27,31 +29,31 @@ var (
// WSFuturesTickerData stores ws ticker data for futures websocket
type WSFuturesTickerData struct {
Time int64 `json:"time"`
Feed string `json:"feed"`
ProductID string `json:"product_id"`
Bid float64 `json:"bid"`
Ask float64 `json:"ask"`
BidSize float64 `json:"bid_size"`
AskSize float64 `json:"ask_size"`
Volume float64 `json:"volume"`
DTM float64 `json:"dtm"`
Leverage string `json:"leverage"`
Index float64 `json:"index"`
Premium float64 `json:"premium"`
Last float64 `json:"last"`
Change float64 `json:"change"`
Suspended bool `json:"suspended"`
Tag string `json:"tag"`
Pair string `json:"pair"`
OpenInterest float64 `json:"openinterest"`
MarkPrice float64 `json:"markPrice"`
MaturityTime int64 `json:"maturityTime"`
FundingRate float64 `json:"funding_rate"`
FundingRatePrediction float64 `json:"funding_rate_prediction"`
RelativeFundingRate float64 `json:"relative_funding_rate"`
RelativeFundingRatePrediction float64 `json:"relative_funding_rate_prediction"`
NextFundingRateTime int64 `json:"next_funding_rate_time"`
Time types.Time `json:"time"`
Feed string `json:"feed"`
ProductID string `json:"product_id"`
Bid float64 `json:"bid"`
Ask float64 `json:"ask"`
BidSize float64 `json:"bid_size"`
AskSize float64 `json:"ask_size"`
Volume float64 `json:"volume"`
DTM float64 `json:"dtm"`
Leverage string `json:"leverage"`
Index float64 `json:"index"`
Premium float64 `json:"premium"`
Last float64 `json:"last"`
Change float64 `json:"change"`
Suspended bool `json:"suspended"`
Tag string `json:"tag"`
Pair string `json:"pair"`
OpenInterest float64 `json:"openinterest"`
MarkPrice float64 `json:"markPrice"`
MaturityTime types.Time `json:"maturityTime"`
FundingRate float64 `json:"funding_rate"`
FundingRatePrediction float64 `json:"funding_rate_prediction"`
RelativeFundingRate float64 `json:"relative_funding_rate"`
RelativeFundingRatePrediction float64 `json:"relative_funding_rate_prediction"`
NextFundingRateTime types.Time `json:"next_funding_rate_time"`
}
// WsFuturesTradeData stores public trade data for futures websocket
@@ -59,14 +61,14 @@ type WsFuturesTradeData struct {
Feed string `json:"feed"`
ProductID string `json:"product_id"`
Trades []struct {
Feed string `json:"feed"`
ProductID string `json:"product_id"`
Side string `json:"side"`
ProductType string `json:"type"`
Seq int64 `json:"seq"`
Time int64 `json:"time"`
Quantity float64 `json:"qty"`
Price float64 `json:"price"`
Feed string `json:"feed"`
ProductID string `json:"product_id"`
Side string `json:"side"`
ProductType string `json:"type"`
Seq int64 `json:"seq"`
Time types.Time `json:"time"`
Quantity float64 `json:"qty"`
Price float64 `json:"price"`
} `json:"trades"`
}
@@ -103,17 +105,17 @@ type WsVerboseOpenOrders struct {
Feed string `json:"feed"`
Account string `json:"account"`
Orders []struct {
Instrument string `json:"instrument"`
Time int64 `json:"time"`
LastUpdateTime int64 `json:"last_update_time"`
Qty float64 `json:"qty"`
Filled float64 `json:"filled"`
LimitPrice float64 `json:"limit_price"`
StopPrice float64 `json:"stop_price"`
OrderType string `json:"type"`
OrderID string `json:"order_id"`
Direction int64 `json:"direction"`
ReduceOnly bool `json:"reduce_only"`
Instrument string `json:"instrument"`
Time types.Time `json:"time"`
LastUpdateTime types.Time `json:"last_update_time"`
Qty float64 `json:"qty"`
Filled float64 `json:"filled"`
LimitPrice float64 `json:"limit_price"`
StopPrice float64 `json:"stop_price"`
OrderType string `json:"type"`
OrderID string `json:"order_id"`
Direction int64 `json:"direction"`
ReduceOnly bool `json:"reduce_only"`
} `json:"orders"`
}
@@ -161,16 +163,16 @@ type WsFuturesFillsData struct {
Feed string `json:"feed"`
Account string `json:"account"`
Fills []struct {
Instrument string `json:"instrument"`
Time int64 `json:"time"`
Price float64 `json:"price"`
Seq int64 `json:"seq"`
Buy bool `json:"buy"`
Quantity float64 `json:"qty"`
OrderID string `json:"order_id"`
ClientOrderID string `json:"cli_order_id"`
FillID string `json:"fill_id"`
FillType string `json:"fill_type"`
Instrument string `json:"instrument"`
Time types.Time `json:"time"`
Price float64 `json:"price"`
Seq int64 `json:"seq"`
Buy bool `json:"buy"`
Quantity float64 `json:"qty"`
OrderID string `json:"order_id"`
ClientOrderID string `json:"cli_order_id"`
FillID string `json:"fill_id"`
FillType string `json:"fill_type"`
} `json:"fills"`
}
@@ -179,17 +181,17 @@ type WsFuturesOpenOrders struct {
Feed string `json:"feed"`
Account string `json:"account"`
Orders []struct {
Instrument string `json:"instrument"`
Time int64 `json:"time"`
LastUpdateTime int64 `json:"last_update_time"`
Qty float64 `json:"qty"`
Filled float64 `json:"filled"`
LimitPrice float64 `json:"limit_price"`
StopPrice float64 `json:"stop_price"`
OrderType string `json:"order_type"`
OrderID string `json:"order_id"`
Direction string `json:"direction"`
ReduceOnly bool `json:"reduce_only"`
Instrument string `json:"instrument"`
Time types.Time `json:"time"`
LastUpdateTime types.Time `json:"last_update_time"`
Qty float64 `json:"qty"`
Filled float64 `json:"filled"`
LimitPrice float64 `json:"limit_price"`
StopPrice float64 `json:"stop_price"`
OrderType string `json:"order_type"`
OrderID string `json:"order_id"`
Direction string `json:"direction"`
ReduceOnly bool `json:"reduce_only"`
} `json:"orders"`
}
@@ -214,11 +216,11 @@ type WsAccountBalancesAndMargin struct {
type WsFuturesNotifications struct {
Feed string `json:"feed"`
Notifications []struct {
ID int64 `json:"id"`
NotificationType string `json:"notificationType"`
Priority string `json:"priority"`
Note string `json:"note"`
EffectiveTime int64 `json:"effective_time"`
ID int64 `json:"id"`
NotificationType string `json:"notificationType"`
Priority string `json:"priority"`
Note string `json:"note"`
EffectiveTime types.Time `json:"effective_time"`
}
}
@@ -229,7 +231,7 @@ type assetTranslatorStore struct {
// FuturesOrderbookData stores orderbook data for futures
type FuturesOrderbookData struct {
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
Orderbook struct {
Bids [][2]float64 `json:"bids"`
Asks [][2]float64 `json:"asks"`
@@ -238,21 +240,21 @@ type FuturesOrderbookData struct {
// TimeResponse type
type TimeResponse struct {
Unixtime int64 `json:"unixtime"`
Rfc1123 string `json:"rfc1123"`
Unixtime types.Time `json:"unixtime"`
Rfc1123 string `json:"rfc1123"`
}
// FuturesInstrumentData stores info for futures market
type FuturesInstrumentData struct {
Instruments []struct {
Symbol string `json:"symbol"`
FutureType string `json:"type"`
Underlying string `json:"underlying"`
OpeningDate string `json:"openingDate"`
LastTradingTime string `json:"lastTradingTime"`
TickSize float64 `json:"tickSize"`
ContractSize float64 `json:"contractSize"`
Tradable bool `json:"tradeable"`
Symbol string `json:"symbol"`
FutureType string `json:"type"`
Underlying string `json:"underlying"`
OpeningDate time.Time `json:"openingDate"`
LastTradingTime time.Time `json:"lastTradingTime"`
TickSize float64 `json:"tickSize"`
ContractSize float64 `json:"contractSize"`
Tradable bool `json:"tradeable"`
MarginLevels []struct {
Contracts float64 `json:"contracts"`
InitialMargin float64 `json:"initialMargin"`
@@ -264,34 +266,34 @@ type FuturesInstrumentData struct {
// FuturesTradeHistoryData stores trade history data for futures
type FuturesTradeHistoryData struct {
History []struct {
Time string `json:"time"`
TradeID int64 `json:"trade_id"`
Price float64 `json:"price"`
Size float64 `json:"size"`
Side string `json:"side"`
TradeType string `json:"type"`
Time time.Time `json:"time"`
TradeID int64 `json:"trade_id"`
Price float64 `json:"price"`
Size float64 `json:"size"`
Side string `json:"side"`
TradeType string `json:"type"`
} `json:"history"`
}
// FuturesTickersData stores info for futures tickers
type FuturesTickersData struct {
Tickers []FuturesTicker `json:"tickers"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesTickerData stores info for futures ticker
type FuturesTickerData struct {
Ticker FuturesTicker `json:"ticker"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesEditedOrderData stores an edited order's data
type FuturesEditedOrderData struct {
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
EditStatus struct {
Status string `json:"status"`
OrderID string `json:"orderId"`
ReceivedTime string `json:"receivedTime"`
Status string `json:"status"`
OrderID string `json:"orderId"`
ReceivedTime time.Time `json:"receivedTime"`
OrderEvents []struct {
Old FuturesOrderData `json:"old"`
New FuturesOrderData `json:"new"`
@@ -303,34 +305,34 @@ type FuturesEditedOrderData struct {
// FuturesTicker holds futures ticker data
type FuturesTicker struct {
Tag string `json:"tag"`
Pair string `json:"pair"`
Symbol string `json:"symbol"`
MarkPrice float64 `json:"markPrice"`
Bid float64 `json:"bid"`
BidSize float64 `json:"bidSize"`
Ask float64 `json:"ask"`
AskSize float64 `json:"askSize"`
Vol24h float64 `json:"vol24h"`
OpenInterest float64 `json:"openInterest"`
Open24H float64 `json:"open24h"`
Last float64 `json:"last"`
LastTime string `json:"lastTime"`
LastSize float64 `json:"lastSize"`
Suspended bool `json:"suspended"`
FundingRate float64 `json:"fundingRate"`
FundingRatePrediction float64 `json:"fundingRatePrediction"`
IndexPrice float64 `json:"indexPrice"`
PostOnly bool `json:"postOnly"`
Change24H float64 `json:"change24h"`
Tag string `json:"tag"`
Pair string `json:"pair"`
Symbol string `json:"symbol"`
MarkPrice float64 `json:"markPrice"`
Bid float64 `json:"bid"`
BidSize float64 `json:"bidSize"`
Ask float64 `json:"ask"`
AskSize float64 `json:"askSize"`
Vol24h float64 `json:"vol24h"`
OpenInterest float64 `json:"openInterest"`
Open24H float64 `json:"open24h"`
Last float64 `json:"last"`
LastTime time.Time `json:"lastTime"`
LastSize float64 `json:"lastSize"`
Suspended bool `json:"suspended"`
FundingRate float64 `json:"fundingRate"`
FundingRatePrediction float64 `json:"fundingRatePrediction"`
IndexPrice float64 `json:"indexPrice"`
PostOnly bool `json:"postOnly"`
Change24H float64 `json:"change24h"`
}
// FuturesSendOrderData stores send order data
type FuturesSendOrderData struct {
SendStatus struct {
OrderID string `json:"order_id"`
Status string `json:"status"`
ReceivedTime string `json:"receivedTime"`
OrderID string `json:"order_id"`
Status string `json:"status"`
ReceivedTime time.Time `json:"receivedTime"`
OrderEvents []struct {
UID string `json:"uid"`
Order FuturesOrderData `json:"order"`
@@ -338,71 +340,71 @@ type FuturesSendOrderData struct {
DataType string `json:"type"`
} `json:"orderEvents"`
} `json:"sendStatus"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesOrderData stores order data
type FuturesOrderData struct {
OrderID string `json:"orderId"`
ClientOrderID string `json:"cliOrderId"`
OrderType string `json:"type"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Quantity float64 `json:"quantity"`
Filled float64 `json:"filled"`
LimitPrice float64 `json:"limitPrice"`
ReduceOnly bool `json:"reduceOnly"`
Timestamp string `json:"timestamp"`
LastUpdateTimestamp string `json:"lastUpdateTimestamp"`
OrderID string `json:"orderId"`
ClientOrderID string `json:"cliOrderId"`
OrderType string `json:"type"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Quantity float64 `json:"quantity"`
Filled float64 `json:"filled"`
LimitPrice float64 `json:"limitPrice"`
ReduceOnly bool `json:"reduceOnly"`
Timestamp time.Time `json:"timestamp"`
LastUpdateTimestamp time.Time `json:"lastUpdateTimestamp"`
}
// FuturesCancelOrderData stores cancel order data for futures
type FuturesCancelOrderData struct {
CancelStatus struct {
Status string `json:"status"`
OrderID string `json:"order_id"`
ReceivedTime string `json:"receivedTime"`
Status string `json:"status"`
OrderID string `json:"order_id"`
ReceivedTime time.Time `json:"receivedTime"`
OrderEvents []struct {
UID string `json:"uid"`
Order FuturesOrderData `json:"order"`
DataType string `json:"type"`
} `json:"orderEvents"`
} `json:"cancelStatus"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesFillsData stores fills data
type FuturesFillsData struct {
Fills []struct {
FillID string `json:"fill_id"`
Symbol string `json:"symbol"`
Side string `json:"buy"`
OrderID string `json:"order_id"`
Size float64 `json:"size"`
Price float64 `json:"price"`
FillTime string `json:"fillTime"`
FillType string `json:"fillType"`
FillID string `json:"fill_id"`
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderID string `json:"order_id"`
Size float64 `json:"size"`
Price float64 `json:"price"`
FillTime time.Time `json:"fillTime"`
FillType string `json:"fillType"`
} `json:"fills"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesTransferData stores transfer data
type FuturesTransferData struct {
Result string `json:"result"`
ServerTime string `json:"serverTime"`
Result string `json:"result"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesOpenPositions stores open positions data for futures
type FuturesOpenPositions struct {
OpenPositions []struct {
Side string `json:"side"`
Symbol string `json:"symbol"`
Price float64 `json:"price"`
FillTime string `json:"fillTime"`
Size float64 `json:"size"`
UnrealizedFunding float64 `json:"unrealizedFunding"`
Side string `json:"side"`
Symbol string `json:"symbol"`
Price float64 `json:"price"`
FillTime time.Time `json:"fillTime"`
Size float64 `json:"size"`
UnrealizedFunding float64 `json:"unrealizedFunding"`
} `json:"openPositions"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesNotificationData stores notification data
@@ -413,7 +415,7 @@ type FuturesNotificationData struct {
Note string `json:"note"`
EffectiveTime string `json:"effectiveTime"`
} `json:"notifications"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesAccountsData stores account data
@@ -449,9 +451,9 @@ type AccountsData struct {
// CancelAllOrdersData stores order data for all cancelled orders
type CancelAllOrdersData struct {
CancelStatus struct {
ReceivedTime string `json:"receivedTime"`
CancelOnly string `json:"cancelOnly"`
Status string `json:"status"`
ReceivedTime time.Time `json:"receivedTime"`
CancelOnly string `json:"cancelOnly"`
Status string `json:"status"`
CancelledOrders []struct {
OrderID string `json:"order_id"`
} `json:"cancelledOrders"`
@@ -461,17 +463,17 @@ type CancelAllOrdersData struct {
Order FuturesOrderData `json:"order"`
DataType string `json:"type"`
} `json:"cancelStatus"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// CancelOrdersAfterData stores data of all orders after a certain time that are cancelled
type CancelOrdersAfterData struct {
Result string `json:"result"`
Status struct {
CurrentTime string `json:"currentTime"`
TriggerTime string `json:"triggerTime"`
CurrentTime time.Time `json:"currentTime"`
TriggerTime time.Time `json:"triggerTime"`
} `json:"status"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// RecentOrderData stores order data of a recent order
@@ -491,20 +493,20 @@ type RecentOrderData struct {
// FOpenOrdersData stores open orders data for futures
type FOpenOrdersData struct {
OrderID string `json:"order_id"`
ClientOrderID string `json:"cliOrdId"`
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderType string `json:"orderType"`
LimitPrice float64 `json:"limitPrice"`
StopPrice float64 `json:"stopPrice"`
UnfilledSize float64 `json:"unfilledSize"`
ReceivedTime string `json:"receivedTime"`
Status string `json:"status"`
FilledSize float64 `json:"filledSize"`
ReduceOnly bool `json:"reduceOnly"`
TriggerSignal string `json:"triggerSignal"`
LastUpdateTime string `json:"lastUpdateTime"`
OrderID string `json:"order_id"`
ClientOrderID string `json:"cliOrdId"`
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderType string `json:"orderType"`
LimitPrice float64 `json:"limitPrice"`
StopPrice float64 `json:"stopPrice"`
UnfilledSize float64 `json:"unfilledSize"`
ReceivedTime time.Time `json:"receivedTime"`
Status string `json:"status"`
FilledSize float64 `json:"filledSize"`
ReduceOnly bool `json:"reduceOnly"`
TriggerSignal string `json:"triggerSignal"`
LastUpdateTime time.Time `json:"lastUpdateTime"`
}
// FuturesRecentOrdersData stores recent orders data
@@ -540,17 +542,17 @@ type FuturesRecentOrdersData struct {
// BatchOrderData stores batch order data
type BatchOrderData struct {
Result string `json:"result"`
ServerTime string `json:"serverTime"`
Result string `json:"result"`
ServerTime time.Time `json:"serverTime"`
BatchStatus []struct {
Status string `json:"status"`
OrderTag string `json:"order_tag"`
OrderID string `json:"order_id"`
DateTimeReceived string `json:"dateTimeReceived"`
Status string `json:"status"`
OrderTag string `json:"order_tag"`
OrderID string `json:"order_id"`
DateTimeReceived time.Time `json:"dateTimeReceived"`
OrderEvents []struct {
OrderPlaced FuturesOrderData `json:"orderPlaced"`
ReduceOnly bool `json:"reduceOnly"`
Timestamp string `json:"timestamp"`
Timestamp time.Time `json:"timestamp"`
OldEditedOrder FuturesOrderData `json:"old"`
NewEditedOrder FuturesOrderData `json:"new"`
UID string `json:"uid"`
@@ -584,7 +586,7 @@ type ExecutionData struct {
type FuturesOpenOrdersData struct {
Result string `json:"result"`
OpenOrders []FOpenOrdersData `json:"openOrders"`
ServerTime string `json:"serverTime"`
ServerTime time.Time `json:"serverTime"`
}
// FuturesPublicTrades returns public trade data
@@ -604,34 +606,34 @@ type FuturesPublicTrades struct {
Quantity float64 `json:"quantity,string"`
TakerOrder FutureTradeOrder `json:"takerOrder"`
TakerOrderData FuturesTradeOrderData `json:"takerOrderData"`
Timestamp int64 `json:"timestamp"`
Timestamp types.Time `json:"timestamp"`
UID string `json:"uid"`
UsdValue float64 `json:"usdValue,string"`
} `json:"execution"`
TakerReducedQuantity string `json:"takerReducedQuantity"`
} `json:"execution"`
} `json:"event"`
Timestamp int64 `json:"timestamp"`
UID string `json:"uid"`
Timestamp types.Time `json:"timestamp"`
UID string `json:"uid"`
} `json:"elements"`
Len int64 `json:"len"`
}
// FutureTradeOrder holds details about the order for a futures trade
type FutureTradeOrder struct {
AccountUID string `json:"accountUid"`
ClientID string `json:"clientId"`
Direction string `json:"direction"`
Filled string `json:"filled"`
LastUpdateTimestamp int64 `json:"lastUpdateTimestamp"`
LimitPrice float64 `json:"limitPrice,string"`
OrderType string `json:"orderType"`
Quantity float64 `json:"quantity,string"`
ReduceOnly bool `json:"reduceOnly"`
SpotData string `json:"spotData"`
Timestamp int64 `json:"timestamp"`
Tradeable string `json:"tradeable"`
UID string `json:"uid"`
AccountUID string `json:"accountUid"`
ClientID string `json:"clientId"`
Direction string `json:"direction"`
Filled string `json:"filled"`
LastUpdateTimestamp types.Time `json:"lastUpdateTimestamp"`
LimitPrice float64 `json:"limitPrice,string"`
OrderType string `json:"orderType"`
Quantity float64 `json:"quantity,string"`
ReduceOnly bool `json:"reduceOnly"`
SpotData string `json:"spotData"`
Timestamp types.Time `json:"timestamp"`
Tradeable string `json:"tradeable"`
UID string `json:"uid"`
}
// FuturesTradeOrderData holds additional order details for a futures trade order
@@ -648,10 +650,10 @@ type FuturesCandles struct {
// FuturesCandle is an individual candle
type FuturesCandle struct {
Close float64 `json:"close,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Open float64 `json:"open,string"`
Time int64 `json:"time"`
Volume float64 `json:"volume,string"`
Close float64 `json:"close,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Open float64 `json:"open,string"`
Time types.Time `json:"time"`
Volume float64 `json:"volume,string"`
}

View File

@@ -1283,7 +1283,7 @@ func TestWsOpenOrders(t *testing.T) {
assert.Equal(t, order.Pending, v.Status, "order status")
assert.Equal(t, 0.0, v.Price, "price")
assert.Equal(t, 0.0001, v.Amount, "amount")
assert.Equal(t, time.UnixMicro(1692851641361371).UTC(), v.Date, "Date")
assert.Equal(t, time.UnixMicro(1692851641361371).UTC(), v.Date.UTC(), "Date")
case 4:
assert.Equal(t, "OKB55A-UEMMN-YUXM2A", v.OrderID, "OrderID")
assert.Equal(t, order.Open, v.Status, "order status")
@@ -1300,7 +1300,7 @@ func TestWsOpenOrders(t *testing.T) {
assert.Equal(t, 0.0001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 26425.2, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, 0.00687, v.Fee, "Fee")
assert.Equal(t, time.UnixMicro(1692851641361447).UTC(), v.LastUpdated, "LastUpdated")
assert.Equal(t, time.UnixMicro(1692851641361447).UTC(), v.LastUpdated.UTC(), "LastUpdated")
case 1:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.UnknownStatus, v.Status, "order status")
@@ -1310,7 +1310,7 @@ func TestWsOpenOrders(t *testing.T) {
case 0:
assert.Equal(t, "OGTT3Y-C6I3P-XRI6HR", v.OrderID, "OrderID")
assert.Equal(t, order.Closed, v.Status, "order status")
assert.Equal(t, time.UnixMicro(1692675961789052).UTC(), v.LastUpdated, "LastUpdated")
assert.Equal(t, time.UnixMicro(1692675961789052).UTC(), v.LastUpdated.UTC(), "LastUpdated")
assert.Equal(t, 10.00345345, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.001, v.Fee, "Fee")
assert.Equal(t, 34.5, v.AverageExecutedPrice, "AverageExecutedPrice")
@@ -1424,7 +1424,7 @@ func TestGetCharts(t *testing.T) {
require.NoError(t, err)
require.NotEmpty(t, resp.Candles)
end := time.UnixMilli(resp.Candles[0].Time)
end := resp.Candles[0].Time.Time()
_, err = k.GetFuturesCharts(t.Context(), "1d", "spot", futuresTestPair, end.Add(-time.Hour*24*7), end)
require.NoError(t, err)
}

View File

@@ -229,13 +229,13 @@ type TradeBalanceInfo struct {
// OrderInfo type
type OrderInfo struct {
RefID string `json:"refid"`
UserRef int32 `json:"userref"`
Status string `json:"status"`
OpenTime float64 `json:"opentm"`
CloseTime float64 `json:"closetm"`
StartTime float64 `json:"starttm"`
ExpireTime float64 `json:"expiretm"`
RefID string `json:"refid"`
UserRef int32 `json:"userref"`
Status string `json:"status"`
OpenTime types.Time `json:"opentm"`
CloseTime types.Time `json:"closetm"`
StartTime types.Time `json:"starttm"`
ExpireTime types.Time `json:"expiretm"`
Description struct {
Pair string `json:"pair"`
Type string `json:"type"`
@@ -303,44 +303,44 @@ type TradesHistory struct {
// TradeInfo type
type TradeInfo struct {
OrderTxID string `json:"ordertxid"`
Pair string `json:"pair"`
Time float64 `json:"time"`
Type string `json:"type"`
OrderType string `json:"ordertype"`
Price float64 `json:"price,string"`
Cost float64 `json:"cost,string"`
Fee float64 `json:"fee,string"`
Volume float64 `json:"vol,string"`
Margin float64 `json:"margin,string"`
Misc string `json:"misc"`
PosTxID string `json:"postxid"`
ClosedPositionAveragePrice float64 `json:"cprice,string"`
ClosedPositionFee float64 `json:"cfee,string"`
ClosedPositionVolume float64 `json:"cvol,string"`
ClosedPositionMargin float64 `json:"cmargin,string"`
Trades []string `json:"trades"`
PosStatus string `json:"posstatus"`
OrderTxID string `json:"ordertxid"`
Pair string `json:"pair"`
Time types.Time `json:"time"`
Type string `json:"type"`
OrderType string `json:"ordertype"`
Price float64 `json:"price,string"`
Cost float64 `json:"cost,string"`
Fee float64 `json:"fee,string"`
Volume float64 `json:"vol,string"`
Margin float64 `json:"margin,string"`
Misc string `json:"misc"`
PosTxID string `json:"postxid"`
ClosedPositionAveragePrice float64 `json:"cprice,string"`
ClosedPositionFee float64 `json:"cfee,string"`
ClosedPositionVolume float64 `json:"cvol,string"`
ClosedPositionMargin float64 `json:"cmargin,string"`
Trades []string `json:"trades"`
PosStatus string `json:"posstatus"`
}
// Position holds the opened position
type Position struct {
Ordertxid string `json:"ordertxid"`
Pair string `json:"pair"`
Time float64 `json:"time"`
Type string `json:"type"`
OrderType string `json:"ordertype"`
Cost float64 `json:"cost,string"`
Fee float64 `json:"fee,string"`
Volume float64 `json:"vol,string"`
VolumeClosed float64 `json:"vol_closed,string"`
Margin float64 `json:"margin,string"`
RolloverTime int64 `json:"rollovertm,string"`
Misc string `json:"misc"`
OrderFlags string `json:"oflags"`
PositionStatus string `json:"posstatus"`
Net string `json:"net"`
Terms string `json:"terms"`
Ordertxid string `json:"ordertxid"`
Pair string `json:"pair"`
Time types.Time `json:"time"`
Type string `json:"type"`
OrderType string `json:"ordertype"`
Cost float64 `json:"cost,string"`
Fee float64 `json:"fee,string"`
Volume float64 `json:"vol,string"`
VolumeClosed float64 `json:"vol_closed,string"`
Margin float64 `json:"margin,string"`
RolloverTime int64 `json:"rollovertm,string"`
Misc string `json:"misc"`
OrderFlags string `json:"oflags"`
PositionStatus string `json:"posstatus"`
Net string `json:"net"`
Terms string `json:"terms"`
}
// GetLedgersOptions type
@@ -361,14 +361,14 @@ type Ledgers struct {
// LedgerInfo type
type LedgerInfo struct {
Refid string `json:"refid"`
Time float64 `json:"time"`
Type string `json:"type"`
Aclass string `json:"aclass"`
Asset string `json:"asset"`
Amount float64 `json:"amount,string"`
Fee float64 `json:"fee,string"`
Balance float64 `json:"balance,string"`
Refid string `json:"refid"`
Time types.Time `json:"time"`
Type string `json:"type"`
Aclass string `json:"aclass"`
Asset string `json:"asset"`
Amount float64 `json:"amount,string"`
Fee float64 `json:"fee,string"`
Balance float64 `json:"balance,string"`
}
// TradeVolumeResponse type
@@ -482,16 +482,16 @@ type DepositAddress struct {
// WithdrawStatusResponse defines a withdrawal status response
type WithdrawStatusResponse struct {
Method string `json:"method"`
Aclass string `json:"aclass"`
Asset string `json:"asset"`
Refid string `json:"refid"`
TxID string `json:"txid"`
Info string `json:"info"`
Amount float64 `json:"amount,string"`
Fee float64 `json:"fee,string"`
Time float64 `json:"time"`
Status string `json:"status"`
Method string `json:"method"`
Aclass string `json:"aclass"`
Asset string `json:"asset"`
Refid string `json:"refid"`
TxID string `json:"txid"`
Info string `json:"info"`
Amount float64 `json:"amount,string"`
Fee float64 `json:"fee,string"`
Time types.Time `json:"time"`
Status string `json:"status"`
}
// WebsocketSubRequest contains request data for Subscribe/Unsubscribe to channels
@@ -552,22 +552,22 @@ type wsSystemStatus struct {
// WsOpenOrder contains all open order data from ws feed
type WsOpenOrder struct {
UserReferenceID int64 `json:"userref"`
ExpireTime float64 `json:"expiretm,string"`
LastUpdated float64 `json:"lastupdated,string"`
OpenTime float64 `json:"opentm,string"`
StartTime float64 `json:"starttm,string"`
Fee float64 `json:"fee,string"`
LimitPrice float64 `json:"limitprice,string"`
StopPrice float64 `json:"stopprice,string"`
Volume float64 `json:"vol,string"`
ExecutedVolume float64 `json:"vol_exec,string"`
Cost float64 `json:"cost,string"`
AveragePrice float64 `json:"avg_price,string"`
Misc string `json:"misc"`
OFlags string `json:"oflags"`
RefID string `json:"refid"`
Status string `json:"status"`
UserReferenceID int64 `json:"userref"`
ExpireTime types.Time `json:"expiretm"`
LastUpdated types.Time `json:"lastupdated"`
OpenTime types.Time `json:"opentm"`
StartTime types.Time `json:"starttm"`
Fee float64 `json:"fee,string"`
LimitPrice float64 `json:"limitprice,string"`
StopPrice float64 `json:"stopprice,string"`
Volume float64 `json:"vol,string"`
ExecutedVolume float64 `json:"vol_exec,string"`
Cost float64 `json:"cost,string"`
AveragePrice float64 `json:"avg_price,string"`
Misc string `json:"misc"`
OFlags string `json:"oflags"`
RefID string `json:"refid"`
Status string `json:"status"`
Description struct {
Close string `json:"close"`
Price float64 `json:"price,string"`
@@ -582,32 +582,32 @@ type WsOpenOrder struct {
// WsOwnTrade ws auth owntrade data
type WsOwnTrade struct {
Cost float64 `json:"cost,string"`
Fee float64 `json:"fee,string"`
Margin float64 `json:"margin,string"`
OrderTransactionID string `json:"ordertxid"`
OrderType string `json:"ordertype"`
Pair string `json:"pair"`
PostTransactionID string `json:"postxid"`
Price float64 `json:"price,string"`
Time float64 `json:"time,string"`
Type string `json:"type"`
Vol float64 `json:"vol,string"`
Cost float64 `json:"cost,string"`
Fee float64 `json:"fee,string"`
Margin float64 `json:"margin,string"`
OrderTransactionID string `json:"ordertxid"`
OrderType string `json:"ordertype"`
Pair string `json:"pair"`
PostTransactionID string `json:"postxid"`
Price float64 `json:"price,string"`
Time types.Time `json:"time"`
Type string `json:"type"`
Vol float64 `json:"vol,string"`
}
// WsOpenOrders ws auth open order data
type WsOpenOrders struct {
Cost float64 `json:"cost,string"`
Description WsOpenOrderDescription `json:"descr"`
ExpireTime time.Time `json:"expiretm"`
ExpireTime types.Time `json:"expiretm"`
Fee float64 `json:"fee,string"`
LimitPrice float64 `json:"limitprice,string"`
Misc string `json:"misc"`
OFlags string `json:"oflags"`
OpenTime time.Time `json:"opentm"`
OpenTime types.Time `json:"opentm"`
Price float64 `json:"price,string"`
RefID string `json:"refid"`
StartTime time.Time `json:"starttm"`
StartTime types.Time `json:"starttm"`
Status string `json:"status"`
StopPrice float64 `json:"stopprice,string"`
UserReference float64 `json:"userref"`

View File

@@ -322,7 +322,7 @@ func (k *Kraken) wsProcessOwnTrades(ownOrders any) error {
TID: key,
Type: oType,
Side: oSide,
Timestamp: convert.TimeFromUnixTimestampDecimal(val.Time),
Timestamp: val.Time.Time(),
}
k.Websocket.DataHandler <- &order.Detail{
Exchange: k.Name,
@@ -357,8 +357,8 @@ func (k *Kraken) wsProcessOpenOrders(ownOrders any) error {
LimitPriceUpper: val.LimitPrice,
ExecutedAmount: val.ExecutedVolume,
Fee: val.Fee,
Date: convert.TimeFromUnixTimestampDecimal(val.OpenTime).Truncate(time.Microsecond),
LastUpdated: convert.TimeFromUnixTimestampDecimal(val.LastUpdated).Truncate(time.Microsecond),
Date: val.OpenTime.Time(),
LastUpdated: val.LastUpdated.Time(),
}
if val.Status != "" {

View File

@@ -603,7 +603,7 @@ func (k *Kraken) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ a
resp[i] = exchange.WithdrawalHistory{
Status: withdrawals[i].Status,
TransferID: withdrawals[i].Refid,
Timestamp: time.Unix(int64(withdrawals[i].Time), 0),
Timestamp: withdrawals[i].Time.Time(),
Amount: withdrawals[i].Amount,
Fee: withdrawals[i].Fee,
CryptoToAddress: withdrawals[i].Info,
@@ -665,7 +665,7 @@ func (k *Kraken) GetRecentTrades(ctx context.Context, p currency.Pair, assetType
Side: side,
Price: tradeData.Elements[i].ExecutionEvent.OuterExecutionHolder.Execution.MakerOrder.LimitPrice,
Amount: tradeData.Elements[i].ExecutionEvent.OuterExecutionHolder.Execution.MakerOrder.Quantity,
Timestamp: time.UnixMilli(tradeData.Elements[i].ExecutionEvent.OuterExecutionHolder.Execution.MakerOrder.Timestamp),
Timestamp: tradeData.Elements[i].ExecutionEvent.OuterExecutionHolder.Execution.MakerOrder.Timestamp.Time(),
})
}
default:
@@ -941,8 +941,8 @@ func (k *Kraken) GetOrderInfo(ctx context.Context, orderID string, _ currency.Pa
Pair: p,
Side: side,
Type: oType,
Date: convert.TimeFromUnixTimestampDecimal(orderInfo.OpenTime),
CloseTime: convert.TimeFromUnixTimestampDecimal(orderInfo.CloseTime),
Date: orderInfo.OpenTime.Time(),
CloseTime: orderInfo.CloseTime.Time(),
Status: status,
Price: price,
Amount: orderInfo.Volume,
@@ -974,17 +974,13 @@ func (k *Kraken) GetOrderInfo(ctx context.Context, orderID string, _ currency.Pa
if err != nil {
return nil, err
}
timeVar, err := time.Parse(krakenFormat, orderInfo.Fills[y].FillTime)
if err != nil {
return nil, err
}
orderDetail = order.Detail{
OrderID: orderID,
Price: orderInfo.Fills[y].Price,
Amount: orderInfo.Fills[y].Size,
Side: oSide,
Type: fillOrderType,
Date: timeVar,
Date: orderInfo.Fills[y].FillTime,
Pair: pair,
Exchange: k.Name,
AssetType: asset.Futures,
@@ -1143,7 +1139,7 @@ func (k *Kraken) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque
RemainingAmount: resp.Open[i].Volume - resp.Open[i].VolumeExecuted,
ExecutedAmount: resp.Open[i].VolumeExecuted,
Exchange: k.Name,
Date: convert.TimeFromUnixTimestampDecimal(resp.Open[i].OpenTime),
Date: resp.Open[i].OpenTime.Time(),
Price: resp.Open[i].Description.Price,
Side: side,
Type: orderType,
@@ -1184,17 +1180,13 @@ func (k *Kraken) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque
if err != nil {
return orders, err
}
timeVar, err := time.Parse(krakenFormat, activeOrders.OpenOrders[a].ReceivedTime)
if err != nil {
return orders, err
}
orders = append(orders, order.Detail{
OrderID: activeOrders.OpenOrders[a].OrderID,
Price: activeOrders.OpenOrders[a].LimitPrice,
Amount: activeOrders.OpenOrders[a].FilledSize,
Side: oSide,
Type: oType,
Date: timeVar,
Date: activeOrders.OpenOrders[a].ReceivedTime,
Pair: fPair,
Exchange: k.Name,
AssetType: asset.Futures,
@@ -1276,8 +1268,8 @@ func (k *Kraken) GetOrderHistory(ctx context.Context, getOrdersRequest *order.Mu
Cost: resp.Closed[i].Cost,
CostAsset: p.Quote,
Exchange: k.Name,
Date: convert.TimeFromUnixTimestampDecimal(resp.Closed[i].OpenTime),
CloseTime: convert.TimeFromUnixTimestampDecimal(resp.Closed[i].CloseTime),
Date: resp.Closed[i].OpenTime.Time(),
CloseTime: resp.Closed[i].CloseTime.Time(),
Price: resp.Closed[i].Description.Price,
Side: side,
Status: status,
@@ -1615,20 +1607,14 @@ func (k *Kraken) GetFuturesContractDetails(ctx context.Context, item asset.Item)
return nil, err
}
var s, e time.Time
if result.Instruments[i].OpeningDate != "" {
s, err = time.Parse(time.RFC3339, result.Instruments[i].OpeningDate)
if err != nil {
return nil, err
}
if !result.Instruments[i].OpeningDate.IsZero() {
s = result.Instruments[i].OpeningDate
}
var ct futures.ContractType
if result.Instruments[i].LastTradingTime == "" || item == asset.PerpetualSwap {
if result.Instruments[i].LastTradingTime.IsZero() || item == asset.PerpetualSwap {
ct = futures.Perpetual
} else {
e, err = time.Parse(time.RFC3339, result.Instruments[i].LastTradingTime)
if err != nil {
return nil, err
}
e = result.Instruments[i].LastTradingTime
switch {
// three day is used for generosity for contract date ranges
case e.Sub(s) <= kline.OneMonth.Duration()+kline.ThreeDay.Duration():

View File

@@ -1,6 +1,7 @@
package types
import (
"errors"
"fmt"
"strconv"
"strings"
@@ -13,6 +14,8 @@ import (
// format requirements.
type Time time.Time
var errInvalidTimestampFormat = errors.New("invalid timestamp format")
// UnmarshalJSON deserializes json, and timestamp information.
func (t *Time) UnmarshalJSON(data []byte) error {
s := string(data)
@@ -21,13 +24,16 @@ func (t *Time) UnmarshalJSON(data []byte) error {
s = s[1 : len(s)-1]
}
switch s {
case "null", "0", "":
if s == "" || s[0] == 'n' || s == "0" {
return nil
}
if target := strings.Index(s, "."); target != -1 {
s = s[:target] + s[target+1:]
if strings.Trim(s, "0") == "" {
return nil
}
}
// Expects a string of length 10 (seconds), 13 (milliseconds), 16 (microseconds), or 19 (nanoseconds) representing a Unix timestamp
@@ -53,7 +59,7 @@ func (t *Time) UnmarshalJSON(data []byte) error {
case 19:
*t = Time(time.Unix(0, unixTS))
default:
return fmt.Errorf("cannot unmarshal %s into Time", data)
return fmt.Errorf("%w: %q", errInvalidTimestampFormat, data)
}
return nil
}

View File

@@ -12,62 +12,47 @@ import (
func TestUnmarshalJSON(t *testing.T) {
t.Parallel()
var testTime Time
require.NoError(t, json.Unmarshal([]byte(`null`), &testTime))
assert.Equal(t, time.Time{}, testTime.Time())
require.NoError(t, json.Unmarshal([]byte(`0`), &testTime))
assert.Equal(t, time.Time{}, testTime.Time())
require.NoError(t, json.Unmarshal([]byte(`""`), &testTime))
assert.Equal(t, time.Time{}, testTime.Time())
require.NoError(t, json.Unmarshal([]byte(`"0"`), &testTime))
assert.Equal(t, time.Time{}, testTime.Time())
// seconds
require.NoError(t, json.Unmarshal([]byte(`"1628736847"`), &testTime))
assert.Equal(t, time.Unix(1628736847, 0), testTime.Time())
// milliseconds
require.NoError(t, json.Unmarshal([]byte(`"1726104395.5"`), &testTime))
assert.Equal(t, time.UnixMilli(1726104395500), testTime.Time())
require.NoError(t, json.Unmarshal([]byte(`"1726104395.56"`), &testTime))
assert.Equal(t, time.UnixMilli(1726104395560), testTime.Time())
require.NoError(t, json.Unmarshal([]byte(`"1628736847325"`), &testTime))
assert.Equal(t, time.UnixMilli(1628736847325), testTime.Time())
// microseconds
require.NoError(t, json.Unmarshal([]byte(`"1628736847325123"`), &testTime))
assert.Equal(t, time.UnixMicro(1628736847325123), testTime.Time())
require.NoError(t, json.Unmarshal([]byte(`"1726106210903.0"`), &testTime))
assert.Equal(t, time.UnixMicro(1726106210903000), testTime.Time())
require.NoError(t, json.Unmarshal([]byte(`"1747278712.09328"`), &testTime))
assert.Equal(t, time.UnixMicro(1747278712093280), testTime.Time())
// nanoseconds
require.NoError(t, json.Unmarshal([]byte(`"1606292218213.4578"`), &testTime))
assert.Equal(t, time.Unix(0, 1606292218213457800), testTime.Time())
require.NoError(t, json.Unmarshal([]byte(`"1606292218213457800"`), &testTime))
assert.Equal(t, time.Unix(0, 1606292218213457800), testTime.Time())
require.ErrorIs(t, json.Unmarshal([]byte(`"blurp"`), &testTime), strconv.ErrSyntax)
require.Error(t, json.Unmarshal([]byte(`"123456"`), &testTime))
// Captures bad syntax when type should be time.Time (RFC3339)
require.ErrorIs(t, json.Unmarshal([]byte(`"2025-03-28T08:00:00Z"`), &testTime), strconv.ErrSyntax)
// parse int failure
require.ErrorIs(t, json.Unmarshal([]byte(`"1606292218213.45.8"`), &testTime), strconv.ErrSyntax)
for _, tc := range []struct {
input string
want time.Time
expError error
}{
{"null", time.Time{}, nil},
{"0", time.Time{}, nil},
{`""`, time.Time{}, nil},
{`"0"`, time.Time{}, nil},
{`"0.0"`, time.Time{}, nil},
{`"0.00000"`, time.Time{}, nil},
{`"0.0.0.0"`, time.Time{}, strconv.ErrSyntax},
{`"0.1"`, time.Time{}, errInvalidTimestampFormat},
{`"1628736847"`, time.Unix(1628736847, 0), nil},
{`"1726104395.5"`, time.UnixMilli(1726104395500), nil},
{`"1726104395.56"`, time.UnixMilli(1726104395560), nil},
{`"1628736847325"`, time.UnixMilli(1628736847325), nil},
{`"1628736847325123"`, time.UnixMicro(1628736847325123), nil},
{`"1726106210903.0"`, time.UnixMicro(1726106210903000), nil},
{`"1747278712.09328"`, time.UnixMicro(1747278712093280), nil},
{`"1606292218213.4578"`, time.Unix(0, 1606292218213457800), nil},
{`"1560516023.070651"`, time.Unix(0, 1560516023070651000), nil},
{`"1606292218213457800"`, time.Unix(0, 1606292218213457800), nil},
{`"blurp"`, time.Time{}, strconv.ErrSyntax},
{`"123456"`, time.Time{}, errInvalidTimestampFormat},
{`"2025-03-28T08:00:00Z"`, time.Time{}, strconv.ErrSyntax}, // RFC3339 format
{`"1606292218213.45.8"`, time.Time{}, strconv.ErrSyntax}, // parse int failure
} {
t.Run(tc.input, func(t *testing.T) {
t.Parallel()
var testTime Time
err := json.Unmarshal([]byte(tc.input), &testTime)
require.ErrorIsf(t, err, tc.expError, "Unmarshal must not error for input %q", tc.input)
assert.Equal(t, tc.want, testTime.Time())
})
}
}
// 6152384 195.5 ns/op 168 B/op 2 allocs/op (current)
// 5030734 240.1 ns/op 168 B/op 2 allocs/op (previous)
// 3948978 303.5 ns/op 168 B/op 2 allocs/op (current) after more stringent checks
// 6152384 195.5 ns/op 168 B/op 2 allocs/op (previous)
func BenchmarkUnmarshalJSON(b *testing.B) {
var testTime Time
for b.Loop() {