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

@@ -13,7 +13,6 @@ import (
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
@@ -125,12 +124,10 @@ var (
errInvalidAssetAmount = errors.New("invalid asset amount")
errIncompleteArguments = errors.New("missing required argument")
errStartTimeOrFromIDNotSet = errors.New("please set StartTime or FromId, but not both")
errUnexpectedKlineDataLength = errors.New("unexpected kline data length")
errMissingRequiredArgumentCoin = errors.New("missing required argument,coin")
errMissingRequiredArgumentNetwork = errors.New("missing required argument,network")
errAmountValueMustBeGreaterThan0 = errors.New("amount must be greater than 0")
errMissingPaymentAccountInfo = errors.New("error: missing payment account")
errUnixMilliSecTypeAssertion = errors.New("error while asserting unix time integer")
errMissingRequiredParameterAddress = errors.New("missing required parameter \"address\"")
errMissingCurrencySymbol = errors.New("missing currency symbol")
errEitherOrderIDOrClientOrderIDIsRequired = errors.New("either order id or client order id is required")
@@ -415,70 +412,8 @@ func (bi *Binanceus) GetSpotKline(ctx context.Context, arg *KlinesRequestParams)
params.Set("endTime", strconv.FormatInt((arg.EndTime).UnixMilli(), 10))
}
path := common.EncodeURLValues(candleStick, params)
var resp any
err = bi.SendHTTPRequest(ctx,
exchange.RestSpotSupplementary,
path,
spotDefaultRate,
&resp)
if err != nil {
return nil, err
}
responseData, ok := resp.([]any)
if !ok {
return nil, common.GetTypeAssertError("[]any", resp, "responseData")
}
klineData := make([]CandleStick, len(responseData))
for x := range responseData {
individualData, ok := responseData[x].([]any)
if !ok {
return nil, common.GetTypeAssertError("[]any", responseData[x], "individualData")
}
if len(individualData) != 12 {
return nil, errUnexpectedKlineDataLength
}
var candle CandleStick
val, ok := individualData[0].(float64)
if !ok {
return nil, errUnixMilliSecTypeAssertion
}
candle.OpenTime = time.UnixMilli(int64(val))
if candle.Open, err = convert.FloatFromString(individualData[1]); err != nil {
return nil, err
}
if candle.High, err = convert.FloatFromString(individualData[2]); err != nil {
return nil, err
}
if candle.Low, err = convert.FloatFromString(individualData[3]); err != nil {
return nil, err
}
if candle.Close, err = convert.FloatFromString(individualData[4]); err != nil {
return nil, err
}
if candle.Volume, err = convert.FloatFromString(individualData[5]); err != nil {
return nil, err
}
val, ok = individualData[6].(float64)
if !ok {
return nil, errUnixMilliSecTypeAssertion
}
candle.CloseTime = time.UnixMilli(int64(val))
if candle.QuoteAssetVolume, err = convert.FloatFromString(individualData[7]); err != nil {
return nil, err
}
if candle.TradeCount, ok = individualData[8].(float64); !ok {
return nil, common.GetTypeAssertError("float64", individualData[8], "trade count")
}
if candle.TakerBuyAssetVolume, err = convert.FloatFromString(individualData[9]); err != nil {
return nil, err
}
if candle.TakerBuyQuoteAssetVolume, err = convert.FloatFromString(individualData[10]); err != nil {
return nil, err
}
klineData[x] = candle
}
return klineData, nil
var resp []CandleStick
return resp, bi.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, spotDefaultRate, &resp)
}
// GetSinglePriceData to get the latest price for a token symbol or symbols.
@@ -1469,10 +1404,10 @@ func (bi *Binanceus) WithdrawalHistory(ctx context.Context, c currency.Code, sta
params.Set("status", status)
}
if !startTime.IsZero() && startTime.Unix() != 0 {
params.Set("startTime", strconv.FormatInt(startTime.UTC().Unix(), 10))
params.Set("startTime", strconv.FormatInt(startTime.Unix(), 10))
}
if !endTime.IsZero() && endTime.Unix() != 0 {
params.Set("endTime", strconv.FormatInt(endTime.UTC().Unix(), 10))
params.Set("endTime", strconv.FormatInt(endTime.Unix(), 10))
}
if offset != 0 {
params.Set("offset", strconv.Itoa(offset))
@@ -1596,11 +1531,11 @@ func (bi *Binanceus) DepositHistory(ctx context.Context, c currency.Code, status
}
}
if !startTime.IsZero() && startTime.Unix() != 0 {
params.Set("startTime", strconv.FormatInt(startTime.UTC().Unix(), 10))
params.Set("startTime", strconv.FormatInt(startTime.Unix(), 10))
}
if !endTime.IsZero() && endTime.Unix() != 0 {
params.Set("endTime", strconv.FormatInt(endTime.UTC().Unix(), 10))
params.Set("endTime", strconv.FormatInt(endTime.Unix(), 10))
}
if offset != 0 {

View File

@@ -7,6 +7,7 @@ import (
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
@@ -200,17 +201,22 @@ type KlinesRequestParams struct {
// CandleStick holds kline data
type CandleStick struct {
OpenTime time.Time
Open float64
High float64
Low float64
Close float64
Volume float64
CloseTime time.Time
QuoteAssetVolume float64
TradeCount float64
TakerBuyAssetVolume float64
TakerBuyQuoteAssetVolume float64
OpenTime types.Time
Open types.Number
High types.Number
Low types.Number
Close types.Number
Volume types.Number
CloseTime types.Time
QuoteAssetVolume types.Number
TradeCount types.Number
TakerBuyAssetVolume types.Number
TakerBuyQuoteAssetVolume types.Number
}
// UnmarshalJSON unmarshals JSON data into a CandleStick struct
func (c *CandleStick) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &[11]any{&c.OpenTime, &c.Open, &c.High, &c.Low, &c.Close, &c.Volume, &c.CloseTime, &c.QuoteAssetVolume, &c.TradeCount, &c.TakerBuyAssetVolume, &c.TakerBuyQuoteAssetVolume})
}
// SymbolPrice represents a symbol and it's price.
@@ -718,15 +724,15 @@ type WithdrawalResponse struct {
// WithdrawStatusResponse defines a withdrawal status response
type WithdrawStatusResponse struct {
ID string `json:"id"`
Amount float64 `json:"amount,string"`
TransactionFee float64 `json:"transactionFee,string"`
Coin string `json:"coin"`
Status int64 `json:"status"`
Address string `json:"address"`
ApplyTime string `json:"applyTime"`
Network string `json:"network"`
TransferType int64 `json:"transferType"`
ID string `json:"id"`
Amount float64 `json:"amount,string"`
TransactionFee float64 `json:"transactionFee,string"`
Coin string `json:"coin"`
Status int64 `json:"status"`
Address string `json:"address"`
ApplyTime types.DateTime `json:"applyTime"`
Network string `json:"network"`
TransferType int64 `json:"transferType"`
}
// FiatAssetRecord asset information for fiat.
@@ -777,16 +783,16 @@ type DepositAddress struct {
// DepositHistory stores deposit history info.
type DepositHistory struct {
Amount string `json:"amount"`
Coin string `json:"coin"`
Network string `json:"network"`
Status int64 `json:"status"`
Address string `json:"address"`
AddressTag string `json:"addressTag"`
TxID string `json:"txId"`
InsertTime int64 `json:"insertTime"`
TransferType int64 `json:"transferType"`
ConfirmTimes string `json:"confirmTimes"`
Amount string `json:"amount"`
Coin string `json:"coin"`
Network string `json:"network"`
Status int64 `json:"status"`
Address string `json:"address"`
AddressTag string `json:"addressTag"`
TxID string `json:"txId"`
InsertTime types.Time `json:"insertTime"`
TransferType int64 `json:"transferType"`
ConfirmTimes string `json:"confirmTimes"`
}
// UserAccountStream represents the response for getting the listen key for the websocket

View File

@@ -406,10 +406,6 @@ func (bi *Binanceus) GetWithdrawalsHistory(ctx context.Context, c currency.Code,
}
resp := make([]exchange.WithdrawalHistory, len(withdrawals))
for i := range withdrawals {
tm, err := time.Parse(time.DateTime, withdrawals[i].ApplyTime)
if err != nil {
return nil, err
}
resp[i] = exchange.WithdrawalHistory{
Status: strconv.FormatInt(withdrawals[i].Status, 10),
TransferID: withdrawals[i].ID,
@@ -419,7 +415,7 @@ func (bi *Binanceus) GetWithdrawalsHistory(ctx context.Context, c currency.Code,
CryptoToAddress: withdrawals[i].Address,
CryptoTxID: withdrawals[i].ID,
CryptoChain: withdrawals[i].Network,
Timestamp: tm,
Timestamp: withdrawals[i].ApplyTime.Time(),
}
}
return resp, nil
@@ -825,12 +821,12 @@ func (bi *Binanceus) GetHistoricCandles(ctx context.Context, pair currency.Pair,
timeSeries := make([]kline.Candle, len(candles))
for x := range candles {
timeSeries[x] = kline.Candle{
Time: candles[x].OpenTime,
Open: candles[x].Open,
High: candles[x].High,
Low: candles[x].Low,
Close: candles[x].Close,
Volume: candles[x].Volume,
Time: candles[x].OpenTime.Time(),
Open: candles[x].Open.Float64(),
High: candles[x].High.Float64(),
Low: candles[x].Low.Float64(),
Close: candles[x].Close.Float64(),
Volume: candles[x].Volume.Float64(),
}
}
return req.ProcessResponse(timeSeries)
@@ -859,12 +855,12 @@ func (bi *Binanceus) GetHistoricCandlesExtended(ctx context.Context, pair curren
for i := range candles {
timeSeries = append(timeSeries, kline.Candle{
Time: candles[i].OpenTime,
Open: candles[i].Open,
High: candles[i].High,
Low: candles[i].Low,
Close: candles[i].Close,
Volume: candles[i].Volume,
Time: candles[i].OpenTime.Time(),
Open: candles[i].Open.Float64(),
High: candles[i].High.Float64(),
Low: candles[i].Low.Float64(),
Close: candles[i].Close.Float64(),
Volume: candles[i].Volume.Float64(),
})
}
}