exchanges: Refactor time handling and other minor improvements (#1948)

* exchanges: Refactor time handling and other minor improvements

- Updated Kraken wrapper to utilise new time handling methods.
- Simplified Kucoin types by removing unnecessary structures and using direct JSON unmarshalling.
- Improved websocket handling in Kucoin to directly parse candlestick data.
- Modified Lbank types to use the new time representation.
- Adjusted Poloniex wrapper and types to utilise the new time handling.
- Updated Yobit types and wrapper to reflect changes in time representation.
- Introduced DateTime type for better handling of specific time formats.
- Added tests for DateTime unmarshalling to ensure correctness.
- Rid UTC().Unix and UTC().UnixMilli as it's not needed
- Correct Huobi timestamp usage for some endpoints.
- Rid RFC3339 time parsing since Go does that automatically.

* exchanges: Refactor JSON unmarshalling for various types and improve test coverage

* linter: Update error message in TestGetKlines

* refactor: Simplify JSON unmarshalling in MovementHistory and improve test assertions in GetKlines

* refactor: Improve JSON unmarshalling for channel name and clarify comment in wsProcessOpenOrders

* refactor: Update time handling in Huobi types to use types.Time for createdAt fields and relax GetLiquidationOrders test

* refactor: Move wsTicker, wsSpread, wsTrades, and wsCandle types to kraken_types.go for better organistion

* refactor: Add validation for underlying parameter in GetExpirationTime and update tests
This commit is contained in:
Adrian Gallagher
2025-07-01 09:11:55 +10:00
committed by GitHub
parent 48a66c9faa
commit 3cc9a2b9e0
92 changed files with 2488 additions and 3276 deletions

View File

@@ -15,7 +15,7 @@ type Ticker struct {
USD float64
BTC float64
ETH float64
Timestamp int64
Timestamp types.Time
}
}
@@ -63,13 +63,13 @@ type OrderbookEntry struct {
// Trade holds trade history for a specific currency pair
type Trade struct {
Timestamp int64 `json:"timestamp"`
Timestampms int64 `json:"timestampms"`
TID int64 `json:"tid"`
Price float64 `json:"price,string"`
Amount float64 `json:"amount,string"`
Exchange string `json:"exchange"`
Type string `json:"type"`
Timestamp types.Time `json:"timestamp"`
TimestampMS types.Time `json:"timestampms"`
TID int64 `json:"tid"`
Price float64 `json:"price,string"`
Amount float64 `json:"amount,string"`
Exchange string `json:"exchange"`
Type string `json:"type"`
}
// Auction is generalized response type
@@ -90,16 +90,16 @@ type Auction struct {
// AuctionHistory holds auction history information
type AuctionHistory struct {
AuctionID int64 `json:"auction_id"`
AuctionPrice float64 `json:"auction_price,string"`
AuctionQuantity float64 `json:"auction_quantity,string"`
EID int64 `json:"eid"`
HighestBidPrice float64 `json:"highest_bid_price,string"`
LowestAskPrice float64 `json:"lowest_ask_price,string"`
AuctionResult string `json:"auction_result"`
Timestamp int64 `json:"timestamp"`
TimestampMS int64 `json:"timestampms"`
EventType string `json:"event_type"`
AuctionID int64 `json:"auction_id"`
AuctionPrice float64 `json:"auction_price,string"`
AuctionQuantity float64 `json:"auction_quantity,string"`
EID int64 `json:"eid"`
HighestBidPrice float64 `json:"highest_bid_price,string"`
LowestAskPrice float64 `json:"lowest_ask_price,string"`
AuctionResult string `json:"auction_result"`
Timestamp types.Time `json:"timestamp"`
TimestampMS types.Time `json:"timestampms"`
EventType string `json:"event_type"`
}
// OrderResult holds cancelled order information
@@ -116,7 +116,7 @@ type OrderResult struct {
type TransferResponse struct {
Type string `json:"type"`
Status string `json:"status"`
Timestamp int64 `json:"timestampms"`
Timestamp types.Time `json:"timestampms"`
EventID int64 `json:"eid"`
DepositAdvanceEventID int64 `json:"advanceEid"`
Currency currency.Code `json:"currency"`
@@ -133,42 +133,42 @@ type TransferResponse struct {
// Order contains order information
type Order struct {
OrderID int64 `json:"order_id,string"`
ID int64 `json:"id,string"`
ClientOrderID string `json:"client_order_id"`
Symbol string `json:"symbol"`
Exchange string `json:"exchange"`
Price float64 `json:"price,string"`
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
Side string `json:"side"`
Type string `json:"type"`
Timestamp int64 `json:"timestamp,string"`
TimestampMS int64 `json:"timestampms"`
IsLive bool `json:"is_live"`
IsCancelled bool `json:"is_cancelled"`
IsHidden bool `json:"is_hidden"`
Options []string `json:"options"`
WasForced bool `json:"was_forced"`
ExecutedAmount float64 `json:"executed_amount,string"`
RemainingAmount float64 `json:"remaining_amount,string"`
OriginalAmount float64 `json:"original_amount,string"`
Message string `json:"message"`
OrderID int64 `json:"order_id,string"`
ID int64 `json:"id,string"`
ClientOrderID string `json:"client_order_id"`
Symbol string `json:"symbol"`
Exchange string `json:"exchange"`
Price float64 `json:"price,string"`
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
Side string `json:"side"`
Type string `json:"type"`
Timestamp types.Time `json:"timestamp"`
TimestampMS types.Time `json:"timestampms"`
IsLive bool `json:"is_live"`
IsCancelled bool `json:"is_cancelled"`
IsHidden bool `json:"is_hidden"`
Options []string `json:"options"`
WasForced bool `json:"was_forced"`
ExecutedAmount float64 `json:"executed_amount,string"`
RemainingAmount float64 `json:"remaining_amount,string"`
OriginalAmount float64 `json:"original_amount,string"`
Message string `json:"message"`
}
// TradeHistory holds trade history information
type TradeHistory struct {
Price float64 `json:"price,string"`
Amount float64 `json:"amount,string"`
Timestamp int64 `json:"timestamp"`
TimestampMS int64 `json:"timestampms"`
Type string `json:"type"`
FeeCurrency string `json:"fee_currency"`
FeeAmount float64 `json:"fee_amount,string"`
TID int64 `json:"tid"`
OrderID int64 `json:"order_id,string"`
Exchange string `json:"exchange"`
IsAuctionFilled bool `json:"is_auction_fill"`
ClientOrderID string `json:"client_order_id"`
Price float64 `json:"price,string"`
Amount float64 `json:"amount,string"`
Timestamp types.Time `json:"timestamp"`
TimestampMS types.Time `json:"timestampms"`
Type string `json:"type"`
FeeCurrency string `json:"fee_currency"`
FeeAmount float64 `json:"fee_amount,string"`
TID int64 `json:"tid"`
OrderID int64 `json:"order_id,string"`
Exchange string `json:"exchange"`
IsAuctionFilled bool `json:"is_auction_fill"`
ClientOrderID string `json:"client_order_id"`
// Used to store values
BaseCurrency string
QuoteCurrency string
@@ -272,11 +272,11 @@ type WsSubscriptionAcknowledgementResponse struct {
// WsHeartbeatResponse Gemini will send a heartbeat every five seconds so you'll know your WebSocket connection is active.
type WsHeartbeatResponse struct {
Type string `json:"type"`
Timestampms int64 `json:"timestampms"`
Sequence int64 `json:"sequence"`
TraceID string `json:"trace_id"`
SocketSequence int64 `json:"socket_sequence"`
Type string `json:"type"`
TimestampMS types.Time `json:"timestampms"`
Sequence int64 `json:"sequence"`
TraceID string `json:"trace_id"`
SocketSequence int64 `json:"socket_sequence"`
}
// WsOrderResponse contains active orders
@@ -285,7 +285,7 @@ type WsOrderResponse struct {
IsCancelled bool `json:"is_cancelled"`
IsHidden bool `json:"is_hidden"`
SocketSequence int64 `json:"socket_sequence"`
Timestampms int64 `json:"timestampms"`
TimestampMS types.Time `json:"timestampms"`
AvgExecutionPrice float64 `json:"avg_execution_price,string"`
ExecutedAmount float64 `json:"executed_amount,string"`
RemainingAmount float64 `json:"remaining_amount,string"`
@@ -300,7 +300,7 @@ type WsOrderResponse struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderType string `json:"order_type"`
Timestamp string `json:"timestamp"`
Timestamp types.Time `json:"timestamp"`
Fill WsOrderFilledData `json:"fill"`
}
@@ -338,25 +338,25 @@ type wsSubscribeRequest struct {
}
type wsTrade struct {
Type string `json:"type"`
Symbol string `json:"symbol"`
EventID int64 `json:"event_id"`
Timestamp int64 `json:"timestamp"`
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,string"`
Side string `json:"side"`
Type string `json:"type"`
Symbol string `json:"symbol"`
EventID int64 `json:"event_id"`
Timestamp types.Time `json:"timestamp"`
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,string"`
Side string `json:"side"`
}
type wsAuctionResult struct {
Type string `json:"type"`
Symbol string `json:"symbol"`
Result string `json:"result"`
TimeMilliseconds int64 `json:"time_ms"`
HighestBidPrice float64 `json:"highest_bid_price,string"`
LowestBidPrice float64 `json:"lowest_ask_price,string"`
CollarPrice float64 `json:"collar_price,string"`
AuctionPrice float64 `json:"auction_price,string"`
AuctionQuantity float64 `json:"auction_quantity,string"`
Type string `json:"type"`
Symbol string `json:"symbol"`
Result string `json:"result"`
TimeMilliseconds types.Time `json:"time_ms"`
HighestBidPrice float64 `json:"highest_bid_price,string"`
LowestBidPrice float64 `json:"lowest_ask_price,string"`
CollarPrice float64 `json:"collar_price,string"`
AuctionPrice float64 `json:"auction_price,string"`
AuctionQuantity float64 `json:"auction_quantity,string"`
}
type wsL2MarketData struct {

View File

@@ -284,7 +284,7 @@ func (g *Gemini) wsHandleData(respRaw []byte) error {
Side: oSide,
Status: oStatus,
AssetType: asset.Spot,
Date: time.UnixMilli(result[i].Timestampms),
Date: result[i].TimestampMS.Time(),
Pair: pair,
}
}
@@ -339,7 +339,7 @@ func (g *Gemini) wsHandleData(respRaw []byte) error {
}
tradeEvent := trade.Data{
Timestamp: time.UnixMilli(result.Timestamp),
Timestamp: result.Timestamp.Time(),
CurrencyPair: pair,
AssetType: asset.Spot,
Exchange: g.Name,
@@ -555,7 +555,7 @@ func (g *Gemini) wsProcessUpdate(result *wsL2MarketData) error {
}
}
trades[x] = trade.Data{
Timestamp: time.UnixMilli(result.Trades[x].Timestamp),
Timestamp: result.Trades[x].Timestamp.Time(),
CurrencyPair: pair,
AssetType: asset.Spot,
Exchange: g.Name,

View File

@@ -344,7 +344,7 @@ func (g *Gemini) GetAccountFundingHistory(ctx context.Context) ([]exchange.Fundi
resp[i] = exchange.FundingHistory{
Status: transfers[i].Status,
TransferID: transfers[i].WithdrawalID,
Timestamp: time.UnixMilli(transfers[i].Timestamp),
Timestamp: transfers[i].Timestamp.Time(),
Currency: transfers[i].Currency.String(),
Amount: transfers[i].Amount,
Fee: transfers[i].FeeAmount,
@@ -373,7 +373,7 @@ func (g *Gemini) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a a
resp = append(resp, exchange.WithdrawalHistory{
Status: transfers[i].Status,
TransferID: transfers[i].WithdrawalID,
Timestamp: time.UnixMilli(transfers[i].Timestamp),
Timestamp: transfers[i].Timestamp.Time(),
Currency: transfers[i].Currency.String(),
Amount: transfers[i].Amount,
Fee: transfers[i].FeeAmount,
@@ -406,16 +406,12 @@ func (g *Gemini) GetHistoricTrades(ctx context.Context, p currency.Pair, assetTy
allTrades:
for {
var tradeData []Trade
tradeData, err = g.GetTrades(ctx,
p.String(),
ts.Unix(),
int64(limit),
false)
tradeData, err = g.GetTrades(ctx, p.String(), ts.Unix(), int64(limit), false)
if err != nil {
return nil, err
}
for i := range tradeData {
tradeTS := time.Unix(tradeData[i].Timestamp, 0)
tradeTS := tradeData[i].Timestamp.Time()
if tradeTS.After(timestampEnd) && !timestampEnd.IsZero() {
break allTrades
}
@@ -569,7 +565,7 @@ func (g *Gemini) GetOrderInfo(ctx context.Context, orderID string, _ currency.Pa
Amount: resp.OriginalAmount,
RemainingAmount: resp.RemainingAmount,
Pair: cp,
Date: time.UnixMilli(resp.TimestampMS),
Date: resp.TimestampMS.Time(),
Price: resp.Price,
HiddenOrder: resp.IsHidden,
ClientOrderID: resp.ClientOrderID,
@@ -678,7 +674,6 @@ func (g *Gemini) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque
if err != nil {
return nil, err
}
orderDate := time.Unix(resp[i].Timestamp, 0)
orders[i] = order.Detail{
Amount: resp[i].OriginalAmount,
@@ -690,7 +685,7 @@ func (g *Gemini) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque
Side: side,
Price: resp[i].Price,
Pair: symbol,
Date: orderDate,
Date: resp[i].Timestamp.Time(),
}
}
return req.Filter(g.Name, orders), nil
@@ -741,14 +736,12 @@ func (g *Gemini) GetOrderHistory(ctx context.Context, req *order.MultiOrderReque
if err != nil {
return nil, err
}
orderDate := time.Unix(trades[i].Timestamp, 0)
detail := order.Detail{
OrderID: strconv.FormatInt(trades[i].OrderID, 10),
Amount: trades[i].Amount,
ExecutedAmount: trades[i].Amount,
Exchange: g.Name,
Date: orderDate,
Date: trades[i].Timestamp.Time(),
Side: side,
Fee: trades[i].FeeAmount,
Price: trades[i].Price,