mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-01 15:10:44 +00:00
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:
@@ -153,48 +153,7 @@ func (by *Bybit) GetKlines(ctx context.Context, category, symbol string, interva
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return processKlineResponse(resp.List)
|
||||
}
|
||||
|
||||
func processKlineResponse(in [][]string) ([]KlineItem, error) {
|
||||
klines := make([]KlineItem, len(in))
|
||||
for x := range in {
|
||||
if len(in[x]) < 5 {
|
||||
return nil, errors.New("invalid kline data")
|
||||
}
|
||||
startTimestamp, err := strconv.ParseInt(in[x][0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klines[x] = KlineItem{StartTime: time.UnixMilli(startTimestamp)}
|
||||
klines[x].Open, err = strconv.ParseFloat(in[x][1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klines[x].High, err = strconv.ParseFloat(in[x][2], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klines[x].Low, err = strconv.ParseFloat(in[x][3], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klines[x].Close, err = strconv.ParseFloat(in[x][4], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(in[x]) == 7 {
|
||||
klines[x].TradeVolume, err = strconv.ParseFloat(in[x][5], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klines[x].Turnover, err = strconv.ParseFloat(in[x][6], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return klines, nil
|
||||
return resp.List, nil
|
||||
}
|
||||
|
||||
// GetInstrumentInfo retrieves the list of instrument details given the category and symbol.
|
||||
@@ -244,7 +203,7 @@ func (by *Bybit) GetMarkPriceKline(ctx context.Context, category, symbol string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return processKlineResponse(resp.List)
|
||||
return resp.List, nil
|
||||
}
|
||||
|
||||
// GetIndexPriceKline query for historical index price klines. Charts are returned in groups based on the requested interval.
|
||||
@@ -272,7 +231,7 @@ func (by *Bybit) GetIndexPriceKline(ctx context.Context, category, symbol string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return processKlineResponse(resp.List)
|
||||
return resp.List, nil
|
||||
}
|
||||
|
||||
// GetOrderBook retrieves for orderbook depth data.
|
||||
|
||||
@@ -75,25 +75,46 @@ func TestGetKlines(t *testing.T) {
|
||||
s = time.Unix(1691897100, 0).Round(kline.FiveMin.Duration())
|
||||
e = time.Unix(1691907100, 0).Round(kline.FiveMin.Duration())
|
||||
}
|
||||
_, err := b.GetKlines(t.Context(), "spot", spotTradablePair.String(), kline.FiveMin, s, e, 100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetKlines(t.Context(), "linear", usdtMarginedTradablePair.String(), kline.FiveMin, s, e, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetKlines(t.Context(), "linear", usdcMarginedTradablePair.String(), kline.FiveMin, s, e, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetKlines(t.Context(), "inverse", inverseTradablePair.String(), kline.FiveMin, s, e, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetKlines(t.Context(), "option", optionsTradablePair.String(), kline.FiveMin, s, e, 5)
|
||||
if err == nil {
|
||||
t.Fatalf("expected 'params error: Category is invalid', but found nil")
|
||||
for _, tc := range []struct {
|
||||
category string
|
||||
pair currency.Pair
|
||||
reqLimit uint64
|
||||
expRespLen int
|
||||
expError error
|
||||
}{
|
||||
{"spot", spotTradablePair, 100, 34, nil}, // TODO: Update expected limit when mock data is updated
|
||||
{"linear", usdtMarginedTradablePair, 5, 5, nil},
|
||||
{"linear", usdcMarginedTradablePair, 5, 5, nil},
|
||||
{"inverse", inverseTradablePair, 5, 5, nil},
|
||||
{"option", optionsTradablePair, 5, 5, errInvalidCategory},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%s-%s", tc.category, tc.pair), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r, err := b.GetKlines(t.Context(), tc.category, tc.pair.String(), kline.FiveMin, s, e, tc.reqLimit)
|
||||
if tc.expError != nil {
|
||||
require.ErrorIs(t, err, tc.expError)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if mockTests {
|
||||
require.Equal(t, tc.expRespLen, len(r))
|
||||
|
||||
switch tc.category {
|
||||
case "spot":
|
||||
assert.Equal(t, KlineItem{StartTime: types.Time(e), Open: 29393.99, High: 29399.76, Low: 29393.98, Close: 29399.76, TradeVolume: 1.168988, Turnover: 34363.5346739}, r[0])
|
||||
case "linear":
|
||||
if tc.pair == usdtMarginedTradablePair {
|
||||
assert.Equal(t, KlineItem{StartTime: types.Time(e), Open: 0.0003, High: 0.0003, Low: 0.0002995, Close: 0.0003, TradeVolume: 55102100, Turnover: 16506.2427}, r[0])
|
||||
return
|
||||
}
|
||||
assert.Equal(t, KlineItem{StartTime: types.Time(e), Open: 239.7, High: 239.7, Low: 239.7, Close: 239.7}, r[0])
|
||||
case "inverse":
|
||||
assert.Equal(t, KlineItem{StartTime: types.Time(e), Open: 0.2908, High: 0.2912, Low: 0.2908, Close: 0.2912, TradeVolume: 5131, Turnover: 17626.40000346}, r[0])
|
||||
}
|
||||
} else {
|
||||
assert.NotEmpty(t, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,29 +116,34 @@ type RestResponse struct {
|
||||
|
||||
// KlineResponse represents a kline item list instance as an array of string.
|
||||
type KlineResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Category string `json:"category"`
|
||||
List [][]string `json:"list"`
|
||||
Symbol string `json:"symbol"`
|
||||
Category string `json:"category"`
|
||||
List []KlineItem `json:"list"`
|
||||
}
|
||||
|
||||
// KlineItem stores an individual kline data item
|
||||
type KlineItem struct {
|
||||
StartTime time.Time
|
||||
Open float64
|
||||
High float64
|
||||
Low float64
|
||||
Close float64
|
||||
StartTime types.Time
|
||||
Open types.Number
|
||||
High types.Number
|
||||
Low types.Number
|
||||
Close types.Number
|
||||
|
||||
// not available for mark and index price kline data
|
||||
TradeVolume float64
|
||||
Turnover float64
|
||||
TradeVolume types.Number
|
||||
Turnover types.Number
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for KlineItem
|
||||
func (k *KlineItem) UnmarshalJSON(data []byte) error {
|
||||
return json.Unmarshal(data, &[7]any{&k.StartTime, &k.Open, &k.High, &k.Low, &k.Close, &k.TradeVolume, &k.Turnover})
|
||||
}
|
||||
|
||||
// MarkPriceKlineResponse represents a kline data item.
|
||||
type MarkPriceKlineResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Category string `json:"category"`
|
||||
List [][]string `json:"list"`
|
||||
Symbol string `json:"symbol"`
|
||||
Category string `json:"category"`
|
||||
List []KlineItem `json:"list"`
|
||||
}
|
||||
|
||||
func constructOrderbook(o *orderbookResponse) (*Orderbook, error) {
|
||||
|
||||
@@ -1420,12 +1420,12 @@ func (by *Bybit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a
|
||||
timeSeries = make([]kline.Candle, len(candles))
|
||||
for x := range candles {
|
||||
timeSeries[x] = kline.Candle{
|
||||
Time: candles[x].StartTime,
|
||||
Open: candles[x].Open,
|
||||
High: candles[x].High,
|
||||
Low: candles[x].Low,
|
||||
Close: candles[x].Close,
|
||||
Volume: candles[x].TradeVolume,
|
||||
Time: candles[x].StartTime.Time(),
|
||||
Open: candles[x].Open.Float64(),
|
||||
High: candles[x].High.Float64(),
|
||||
Low: candles[x].Low.Float64(),
|
||||
Close: candles[x].Close.Float64(),
|
||||
Volume: candles[x].TradeVolume.Float64(),
|
||||
}
|
||||
}
|
||||
return req.ProcessResponse(timeSeries)
|
||||
@@ -1461,12 +1461,12 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
|
||||
|
||||
for i := range klineItems {
|
||||
timeSeries = append(timeSeries, kline.Candle{
|
||||
Time: klineItems[i].StartTime,
|
||||
Open: klineItems[i].Open,
|
||||
High: klineItems[i].High,
|
||||
Low: klineItems[i].Low,
|
||||
Close: klineItems[i].Close,
|
||||
Volume: klineItems[i].TradeVolume,
|
||||
Time: klineItems[i].StartTime.Time(),
|
||||
Open: klineItems[i].Open.Float64(),
|
||||
High: klineItems[i].High.Float64(),
|
||||
Low: klineItems[i].Low.Float64(),
|
||||
Close: klineItems[i].Close.Float64(),
|
||||
Volume: klineItems[i].TradeVolume.Float64(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user