exchanges: add setTimeWindow boolean to GetKlineRequest param (#1160)

* exchanges: add setTimeWindow boolean to GetKlineRequest params to differentiate between a set time period return from endpoint.

* glorious: nits

* exchange: conjugation

* Update exchanges/exchange.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits and an assortment of differences

* exchanges: remove some comments

* glorious: nits

* cleanup

* tests: fix

* Update exchanges/hitbtc/hitbtc_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/kline/kline.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/kline/kline_test.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

* kline: fix test

* rm unused variables

* almost: nits

* glorious: nits

* linter: fix

* rm unused variable

* Refactored comment in the okex tests to ensure that it accurately reflects the variable name and the issue related to the time window, as requested by GloriousCode. The previous comment did not align with the identifier assigned to the property, which could cause confusion and misunderstanding among other programmers or stakeholders. The updated comment will improve the clarity and readability of the codebase and make it easier to understand the intended purpose of the associated variables. The change was made with the aim of improving the overall quality and maintainability of the code.

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
Ryan O'Hara-Reid
2023-04-27 10:10:19 +10:00
committed by GitHub
parent 668d083749
commit 42475bf2b8
36 changed files with 809 additions and 471 deletions

View File

@@ -815,12 +815,19 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange,
if cfg.DataSettings.APIData.InclusiveEndDate {
cfg.DataSettings.APIData.EndDate = cfg.DataSettings.APIData.EndDate.Add(cfg.DataSettings.Interval.Duration())
}
var limit int64
limit, err = b.Features.Enabled.Kline.GetIntervalResultLimit(cfg.DataSettings.Interval)
if err != nil {
return nil, err
}
resp, err = loadAPIData(
cfg,
exch,
fPair,
a,
b.Features.Enabled.Kline.ResultLimit,
uint32(limit),
dataType)
if err != nil {
return resp, err

View File

@@ -2530,7 +2530,7 @@ func TestGetTechnicalAnalysis(t *testing.T) {
Enabled: currency.Pairs{cp},
}
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.OneDay)
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.IntervalCapacity{Interval: kline.OneDay})
err = em.Add(fExchange{IBotExchange: exch})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)

View File

@@ -167,23 +167,23 @@ func (b *Binance) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.EightHour,
kline.TwelveHour,
kline.OneDay,
kline.ThreeDay,
kline.OneWeek,
kline.OneMonth,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.EightHour},
kline.IntervalCapacity{Interval: kline.TwelveHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.ThreeDay},
kline.IntervalCapacity{Interval: kline.OneWeek},
kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 1000,
GlobalResultLimit: 1000,
},
},
}
@@ -1686,7 +1686,7 @@ func (b *Binance) FormatExchangeKlineInterval(interval kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (b *Binance) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := b.GetKlineRequest(pair, a, interval, start, end)
req, err := b.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
@@ -1701,7 +1701,7 @@ func (b *Binance) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
Symbol: req.Pair,
StartTime: req.Start,
EndTime: req.End,
Limit: int(b.Features.Enabled.Kline.ResultLimit),
Limit: int(req.RequestLimit),
})
if err != nil {
return nil, err
@@ -1741,7 +1741,7 @@ func (b *Binance) GetHistoricCandlesExtended(ctx context.Context, pair currency.
Symbol: req.Pair,
StartTime: req.RangeHolder.Ranges[x].Start.Time,
EndTime: req.RangeHolder.Ranges[x].End.Time,
Limit: int(b.Features.Enabled.Kline.ResultLimit),
Limit: int(req.RequestLimit),
})
if err != nil {
return nil, err

View File

@@ -124,23 +124,23 @@ func (bi *Binanceus) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.EightHour,
kline.TwelveHour,
kline.OneDay,
kline.ThreeDay,
kline.OneWeek,
kline.OneMonth,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.EightHour},
kline.IntervalCapacity{Interval: kline.TwelveHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.ThreeDay},
kline.IntervalCapacity{Interval: kline.OneWeek},
kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 1000,
GlobalResultLimit: 1000,
},
},
}
@@ -869,7 +869,7 @@ func (bi *Binanceus) ValidateCredentials(ctx context.Context, assetType asset.It
// GetHistoricCandles returns candles between a time period for a set time interval
func (bi *Binanceus) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := bi.GetKlineRequest(pair, a, interval, start, end)
req, err := bi.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
@@ -879,7 +879,7 @@ func (bi *Binanceus) GetHistoricCandles(ctx context.Context, pair currency.Pair,
Symbol: req.Pair,
StartTime: req.Start,
EndTime: req.End,
Limit: int64(bi.Features.Enabled.Kline.ResultLimit),
Limit: req.RequestLimit,
})
if err != nil {
return nil, err
@@ -914,7 +914,7 @@ func (bi *Binanceus) GetHistoricCandlesExtended(ctx context.Context, pair curren
Symbol: req.Pair,
StartTime: req.RangeHolder.Ranges[x].Start.Time,
EndTime: req.RangeHolder.Ranges[x].End.Time,
Limit: int64(bi.Features.Enabled.Kline.ResultLimit),
Limit: req.RequestLimit,
})
if err != nil {
return nil, err

View File

@@ -147,20 +147,20 @@ func (b *Bitfinex) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.ThreeHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.OneWeek,
kline.TwoWeek,
kline.OneMonth,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.ThreeHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.TwelveHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.OneWeek},
kline.IntervalCapacity{Interval: kline.TwoWeek},
kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 10000,
GlobalResultLimit: 10000,
},
},
}
@@ -1084,7 +1084,7 @@ func (b *Bitfinex) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (b *Bitfinex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := b.GetKlineRequest(pair, a, interval, start, end)
req, err := b.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
@@ -1099,7 +1099,8 @@ func (b *Bitfinex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
b.FormatExchangeKlineInterval(req.ExchangeInterval),
req.Start.UnixMilli(),
req.End.UnixMilli(),
b.Features.Enabled.Kline.ResultLimit, true)
uint32(req.RequestLimit),
true)
if err != nil {
return nil, err
}
@@ -1138,7 +1139,7 @@ func (b *Bitfinex) GetHistoricCandlesExtended(ctx context.Context, pair currency
b.FormatExchangeKlineInterval(req.ExchangeInterval),
req.RangeHolder.Ranges[x].Start.Ticks*1000,
req.RangeHolder.Ranges[x].End.Ticks*1000,
b.Features.Enabled.Kline.ResultLimit,
uint32(req.RequestLimit),
true)
if err != nil {
return nil, err

View File

@@ -53,7 +53,7 @@ func TestMain(m *testing.M) {
err = b.UpdateTradablePairs(context.Background(), false)
if err != nil {
log.Fatal("Bithumb Setup() init error")
log.Fatal("Bithumb Setup() init error", err)
}
os.Exit(m.Run())
@@ -617,8 +617,8 @@ func TestGetHistoricCandles(t *testing.T) {
if err != nil {
t.Fatal(err)
}
startTime := time.Now().AddDate(0, 0, -1)
_, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneMin, startTime, time.Now())
startTime := time.Now().AddDate(0, -2, 0)
_, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, startTime, time.Now())
if err != nil {
t.Fatal(err)
}

View File

@@ -115,17 +115,22 @@ func (b *Bithumb) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.TenMin,
kline.ThirtyMin,
kline.OneHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.TenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
// NOTE: The supported time intervals below are returned
// offset to the Asia/Seoul time zone. This may lead to
// issues with candle quality and conversion as the
// intervals may be broken up. Therefore the below intervals
// are constructed from hourly candles.
// kline.IntervalCapacity{Interval: kline.SixHour},
// kline.IntervalCapacity{Interval: kline.TwelveHour},
// kline.IntervalCapacity{Interval: kline.OneDay},
),
ResultLimit: 1500,
GlobalResultLimit: 1500,
},
},
}
@@ -784,7 +789,7 @@ func (b *Bithumb) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (b *Bithumb) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := b.GetKlineRequest(pair, a, interval, start, end)
req, err := b.GetKlineRequest(pair, a, interval, start, end, true)
if err != nil {
return nil, err
}

View File

@@ -111,20 +111,20 @@ func (b *Bitstamp) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.ThreeDay,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.TwelveHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.ThreeDay},
),
ResultLimit: 1000,
GlobalResultLimit: 1000,
},
},
}
@@ -855,7 +855,7 @@ func (b *Bitstamp) ValidateCredentials(ctx context.Context, assetType asset.Item
// GetHistoricCandles returns candles between a time period for a set time interval
func (b *Bitstamp) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := b.GetKlineRequest(pair, a, interval, start, end)
req, err := b.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
@@ -865,7 +865,7 @@ func (b *Bitstamp) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
req.Start,
req.End,
b.FormatExchangeKlineInterval(req.ExchangeInterval),
strconv.FormatInt(int64(b.Features.Enabled.Kline.ResultLimit), 10))
strconv.FormatInt(req.RequestLimit, 10))
if err != nil {
return nil, err
}
@@ -903,7 +903,7 @@ func (b *Bitstamp) GetHistoricCandlesExtended(ctx context.Context, pair currency
req.RangeHolder.Ranges[x].Start.Time,
req.RangeHolder.Ranges[x].End.Time,
b.FormatExchangeKlineInterval(req.ExchangeInterval),
strconv.FormatInt(int64(b.Features.Enabled.Kline.ResultLimit), 10),
strconv.FormatInt(req.RequestLimit, 10),
)
if err != nil {
return nil, err

View File

@@ -697,20 +697,43 @@ func TestGetHistoricTrades(t *testing.T) {
}
func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
pair, err := currency.NewPairFromString("btc-usdt")
if err != nil {
t.Fatal(err)
}
start := time.Unix(1546300800, 0)
end := time.Unix(1577836799, 0)
end := start.AddDate(0, 12, 0)
_, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, start, end)
if err != nil {
t.Fatal(err)
}
end = time.Now()
start = end.AddDate(0, -12, 0)
_, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, start, end)
if err != nil {
t.Fatal(err)
}
end = time.Now()
start = end.AddDate(0, 0, -30)
_, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneHour, start, end)
if err != nil {
t.Fatal(err)
}
end = time.Now()
start = end.AddDate(0, 0, -1).Add(time.Minute * 5)
_, err = b.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.FiveMin, start, end)
if err != nil {
t.Fatal(err)
}
}
func TestGetHistoricCandlesExtended(t *testing.T) {
t.Parallel()
pair, err := currency.NewPairFromString("btc-usdt")
if err != nil {
t.Fatal(err)

View File

@@ -28,6 +28,12 @@ import (
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
var (
oneDay = time.Hour * 24
oneMonth = oneDay * 31
oneYear = oneDay * 366
)
// GetDefaultConfig returns a default exchange config
func (b *Bittrex) GetDefaultConfig(ctx context.Context) (*config.Exchange, error) {
b.SetDefaults()
@@ -111,12 +117,11 @@ func (b *Bittrex) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.FiveMin,
kline.OneHour,
kline.OneDay,
kline.IntervalCapacity{Interval: kline.OneMin, Capacity: 1440}, // 1m interval: candles for 1 day (0:00 - 23:59)
kline.IntervalCapacity{Interval: kline.FiveMin, Capacity: 288}, // 5m interval: candles for 1 day (0:00 - 23:55)
kline.IntervalCapacity{Interval: kline.OneHour, Capacity: 744}, // 1 hour interval: candles for 31 days (0:00 - 23:00)
kline.IntervalCapacity{Interval: kline.OneDay, Capacity: 366}, // 1 day interval: candles for 366 days
),
ResultLimit: 1000,
},
},
}
@@ -954,39 +959,36 @@ func (b *Bittrex) FormatExchangeKlineInterval(in kline.Interval) string {
// This implementation rounds returns candles up to the next interval or to the end
// time (whichever comes first)
func (b *Bittrex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := b.GetKlineRequest(pair, a, interval, start, end)
req, err := b.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
candleInterval := b.FormatExchangeKlineInterval(req.ExchangeInterval)
if candleInterval == "notfound" {
return nil, errors.New("invalid interval")
return nil, fmt.Errorf("%w %v", kline.ErrInvalidInterval, interval)
}
year, month, day := req.Start.Date()
year, month, day := req.End.Date()
curYear, curMonth, curDay := time.Now().Date()
getHistoric := false // nolint:ifshort,nolintlint // false positive and triggers only on Windows
getRecent := false // nolint:ifshort,nolintlint // false positive and triggers only on Windows
var getHistoric, getRecent bool
switch req.ExchangeInterval {
case kline.OneMin, kline.FiveMin:
if time.Since(req.Start) > 24*time.Hour {
if time.Since(req.Start) > oneDay {
getHistoric = true
}
if year >= curYear && month >= curMonth && day >= curDay {
getRecent = true
}
case kline.OneHour:
if time.Since(req.Start) > 31*24*time.Hour {
if time.Since(req.Start) > oneMonth {
getHistoric = true
}
if year >= curYear && month >= curMonth {
getRecent = true
}
case kline.OneDay:
if time.Since(req.Start) > 366*24*time.Hour {
if time.Since(req.Start) > oneYear {
getHistoric = true
}
if year >= curYear {
@@ -994,46 +996,48 @@ func (b *Bittrex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
}
}
var ohlcData []CandleData
if !getHistoric && !getRecent {
return nil, errors.New("start end time range cannot get historic or recent candles")
}
var candleData []CandleData
if getHistoric {
var historicData []CandleData
historicData, err = b.GetHistoricalCandles(ctx,
historicData, err := b.GetHistoricalCandles(ctx,
req.RequestFormatted.String(),
b.FormatExchangeKlineInterval(req.ExchangeInterval),
candleInterval,
"TRADE",
year,
int(month),
day)
start.Year(),
int(start.Month()),
start.Day())
if err != nil {
return nil, err
}
ohlcData = append(ohlcData, historicData...)
candleData = append(candleData, historicData...)
}
if getRecent {
var recentData []CandleData
recentData, err = b.GetRecentCandles(ctx,
recentData, err := b.GetRecentCandles(ctx,
req.RequestFormatted.String(),
b.FormatExchangeKlineInterval(req.ExchangeInterval),
candleInterval,
"TRADE")
if err != nil {
return nil, err
}
ohlcData = append(ohlcData, recentData...)
candleData = append(candleData, recentData...)
}
timeSeries := make([]kline.Candle, 0, len(ohlcData))
for x := range ohlcData {
if ohlcData[x].StartsAt.Before(req.Start) ||
ohlcData[x].StartsAt.After(req.End) {
timeSeries := make([]kline.Candle, 0, len(candleData))
for x := range candleData {
if candleData[x].StartsAt.Before(req.Start) || candleData[x].StartsAt.After(req.End) {
continue
}
timeSeries = append(timeSeries, kline.Candle{
Time: ohlcData[x].StartsAt,
Open: ohlcData[x].Open,
High: ohlcData[x].High,
Low: ohlcData[x].Low,
Close: ohlcData[x].Close,
Volume: ohlcData[x].Volume,
Time: candleData[x].StartsAt,
Open: candleData[x].Open,
High: candleData[x].High,
Low: candleData[x].Low,
Close: candleData[x].Close,
Volume: candleData[x].Volume,
})
}
return req.ProcessResponse(timeSeries)

View File

@@ -116,21 +116,21 @@ func (b *BTCMarkets) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.ThreeHour,
kline.FourHour,
kline.SixHour,
kline.OneDay,
kline.OneWeek,
kline.OneMonth,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.ThreeHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.OneWeek},
kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 1000,
GlobalResultLimit: 1000,
},
},
}
@@ -1016,7 +1016,7 @@ func (b *BTCMarkets) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (b *BTCMarkets) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := b.GetKlineRequest(pair, a, interval, start, end)
req, err := b.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}

View File

@@ -130,15 +130,15 @@ func (b *BTSE) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.SixHour,
kline.OneDay,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.OneDay},
),
ResultLimit: 300,
GlobalResultLimit: 300,
},
},
}
@@ -972,7 +972,7 @@ func (b *BTSE) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (b *BTSE) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := b.GetKlineRequest(pair, a, interval, start, end)
req, err := b.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
@@ -988,7 +988,7 @@ func (b *BTSE) GetHistoricCandles(ctx context.Context, pair currency.Pair, a ass
req, err := b.OHLCV(ctx,
req.RequestFormatted.String(),
req.Start,
req.End,
req.End.Add(-req.ExchangeInterval.Duration()), // End time is inclusive so we need to subtract the interval.
intervalInt)
if err != nil {
return nil, err

View File

@@ -136,21 +136,21 @@ func (by *Bybit) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.OneWeek,
kline.OneMonth,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.TwelveHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.OneWeek},
kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 200,
GlobalResultLimit: 200,
},
},
}
@@ -1834,7 +1834,7 @@ func (by *Bybit) FormatExchangeKlineIntervalFutures(ctx context.Context, interva
// GetHistoricCandles returns candles between a time period for a set time interval
func (by *Bybit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := by.GetKlineRequest(pair, a, interval, start, end)
req, err := by.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
@@ -1846,7 +1846,7 @@ func (by *Bybit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a
candles, err = by.GetKlines(ctx,
req.RequestFormatted.String(),
by.FormatExchangeKlineInterval(ctx, req.ExchangeInterval),
int64(by.Features.Enabled.Kline.ResultLimit),
req.RequestLimit,
req.Start,
req.End)
if err != nil {
@@ -1869,7 +1869,7 @@ func (by *Bybit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a
candles, err = by.GetFuturesKlineData(ctx,
req.RequestFormatted,
by.FormatExchangeKlineIntervalFutures(ctx, req.ExchangeInterval),
int64(by.Features.Enabled.Kline.ResultLimit),
req.RequestLimit,
req.Start)
if err != nil {
return nil, err
@@ -1891,7 +1891,7 @@ func (by *Bybit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a
candles, err = by.GetUSDTFuturesKlineData(ctx,
req.RequestFormatted,
by.FormatExchangeKlineIntervalFutures(ctx, req.ExchangeInterval),
int64(by.Features.Enabled.Kline.ResultLimit),
req.RequestLimit,
req.Start)
if err != nil {
return nil, err
@@ -1914,7 +1914,7 @@ func (by *Bybit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a
req.RequestFormatted,
by.FormatExchangeKlineIntervalFutures(ctx, req.ExchangeInterval),
req.Start,
int64(by.Features.Enabled.Kline.ResultLimit))
req.RequestLimit)
if err != nil {
return nil, err
}
@@ -1951,7 +1951,7 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
candles, err = by.GetKlines(ctx,
req.RequestFormatted.String(),
by.FormatExchangeKlineInterval(ctx, req.ExchangeInterval),
int64(by.Features.Enabled.Kline.ResultLimit),
req.RequestLimit,
req.RangeHolder.Ranges[x].Start.Time,
req.RangeHolder.Ranges[x].End.Time)
if err != nil {
@@ -1973,7 +1973,7 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
candles, err = by.GetFuturesKlineData(ctx,
req.RequestFormatted,
by.FormatExchangeKlineIntervalFutures(ctx, req.ExchangeInterval),
int64(by.Features.Enabled.Kline.ResultLimit),
req.RequestLimit,
req.RangeHolder.Ranges[x].Start.Time)
if err != nil {
return nil, err
@@ -1994,7 +1994,7 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
candles, err = by.GetUSDTFuturesKlineData(ctx,
req.RequestFormatted,
by.FormatExchangeKlineIntervalFutures(ctx, req.ExchangeInterval),
int64(by.Features.Enabled.Kline.ResultLimit),
req.RequestLimit,
req.RangeHolder.Ranges[x].Start.Time)
if err != nil {
return nil, err
@@ -2016,7 +2016,7 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
req.RequestFormatted,
by.FormatExchangeKlineIntervalFutures(ctx, req.ExchangeInterval),
req.RangeHolder.Ranges[x].Start.Time,
int64(by.Features.Enabled.Kline.ResultLimit))
req.RequestLimit)
if err != nil {
return nil, err
}

View File

@@ -118,14 +118,14 @@ func (c *CoinbasePro) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.FiveMin,
kline.FifteenMin,
kline.OneHour,
kline.SixHour,
kline.OneDay,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.OneDay},
),
ResultLimit: 300,
GlobalResultLimit: 300,
},
},
}
@@ -892,7 +892,7 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.GetOrdersR
// GetHistoricCandles returns a set of candle between two time periods for a
// designated time period
func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := c.GetKlineRequest(pair, a, interval, start, end)
req, err := c.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}

View File

@@ -1500,7 +1500,7 @@ func (b *Base) IsPerpetualFutureCurrency(asset.Item, currency.Pair) (bool, error
// GetKlineRequest returns a helper for the fetching of candle/kline data for
// a single request within a pre-determined time window.
func (b *Base) GetKlineRequest(pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Request, error) {
func (b *Base) GetKlineRequest(pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time, fixedAPICandleLength bool) (*kline.Request, error) {
if pair.IsEmpty() {
return nil, currency.ErrCurrencyPairEmpty
}
@@ -1516,17 +1516,6 @@ func (b *Base) GetKlineRequest(pair currency.Pair, a asset.Item, interval kline.
return nil, err
}
// NOTE: This check is here to make sure a client is notified that using
// this functionality will result in error if the total candles cannot be
// theoretically retrieved.
if count := kline.TotalCandlesPerInterval(start, end, exchangeInterval); count >
int64(b.Features.Enabled.Kline.ResultLimit) {
return nil, fmt.Errorf("candles count: %d, max limit: %d %w",
count,
b.Features.Enabled.Kline.ResultLimit,
kline.ErrRequestExceedsExchangeLimits)
}
err = b.ValidateKline(pair, a, exchangeInterval)
if err != nil {
return nil, err
@@ -1537,7 +1526,49 @@ func (b *Base) GetKlineRequest(pair currency.Pair, a asset.Item, interval kline.
return nil, err
}
return kline.CreateKlineRequest(b.Name, pair, formatted, a, interval, exchangeInterval, start, end)
limit, err := b.Features.Enabled.Kline.GetIntervalResultLimit(exchangeInterval)
if err != nil {
return nil, err
}
req, err := kline.CreateKlineRequest(b.Name, pair, formatted, a, interval, exchangeInterval, start, end, limit)
if err != nil {
return nil, err
}
// NOTE: The checks below makes sure a client is notified that using this
// functionality will result in error if the total candles cannot be
// theoretically retrieved.
if fixedAPICandleLength {
origCount := kline.TotalCandlesPerInterval(req.Start, req.End, interval)
modifiedCount := kline.TotalCandlesPerInterval(req.Start, time.Now(), exchangeInterval)
if modifiedCount > limit {
errMsg := fmt.Sprintf("for %v %v candles between %v-%v. ",
origCount,
interval,
start.Format(common.SimpleTimeFormatWithTimezone),
end.Format(common.SimpleTimeFormatWithTimezone))
if interval != exchangeInterval {
errMsg += fmt.Sprintf("Request converts to %v %v candles. ",
modifiedCount,
exchangeInterval)
}
boundary := time.Now().Add(-exchangeInterval.Duration() * time.Duration(limit))
return nil, fmt.Errorf("%w %v, exceeding the limit of %v %v candles up to %v. Please reduce timeframe or use GetHistoricCandlesExtended",
kline.ErrRequestExceedsExchangeLimits,
errMsg,
limit,
exchangeInterval,
boundary.Format(common.SimpleTimeFormatWithTimezone))
}
} else if count := kline.TotalCandlesPerInterval(req.Start, req.End, exchangeInterval); count > limit {
return nil, fmt.Errorf("candle count exceeded: %d. The endpoint has a set candle limit return of %d candles. Candle data will be incomplete: %w",
count,
limit,
kline.ErrRequestExceedsExchangeLimits)
}
return req, nil
}
// GetKlineExtendedRequest returns a helper for the fetching of candle/kline
@@ -1566,12 +1597,18 @@ func (b *Base) GetKlineExtendedRequest(pair currency.Pair, a asset.Item, interva
return nil, err
}
r, err := kline.CreateKlineRequest(b.Name, pair, formatted, a, interval, exchangeInterval, start, end)
limit, err := b.Features.Enabled.Kline.GetIntervalResultLimit(exchangeInterval)
if err != nil {
return nil, err
}
r, err := kline.CreateKlineRequest(b.Name, pair, formatted, a, interval, exchangeInterval, start, end, limit)
if err != nil {
return nil, err
}
r.IsExtended = true
dates, err := r.GetRanges(b.Features.Enabled.Kline.ResultLimit)
dates, err := r.GetRanges(uint32(limit))
if err != nil {
return nil, err
}

View File

@@ -1948,7 +1948,7 @@ func TestBase_ValidateKline(t *testing.T) {
Features: Features{
Enabled: FeaturesEnabled{
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(kline.OneMin),
Intervals: kline.DeployExchangeIntervals(kline.IntervalCapacity{Interval: kline.OneMin}),
},
},
},
@@ -2685,42 +2685,17 @@ func TestHasAssetTypeAccountSegregation(t *testing.T) {
func TestGetKlineRequest(t *testing.T) {
t.Parallel()
b := Base{Name: "klineTest"}
_, err := b.GetKlineRequest(currency.EMPTYPAIR, asset.Empty, 0, time.Time{}, time.Time{})
_, err := b.GetKlineRequest(currency.EMPTYPAIR, asset.Empty, 0, time.Time{}, time.Time{}, false)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v' but expected: '%v'", err, currency.ErrCurrencyPairEmpty)
}
pair := currency.NewPair(currency.BTC, currency.USDT)
_, err = b.GetKlineRequest(pair, asset.Empty, 0, time.Time{}, time.Time{})
_, err = b.GetKlineRequest(pair, asset.Empty, 0, time.Time{}, time.Time{}, false)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
_, err = b.GetKlineRequest(pair, asset.Spot, 0, time.Time{}, time.Time{})
if !errors.Is(err, kline.ErrInvalidInterval) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrInvalidInterval)
}
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneMin, time.Time{}, time.Time{})
if !errors.Is(err, kline.ErrCannotConstructInterval) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrCannotConstructInterval)
}
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.OneMin)
b.Features.Enabled.Kline.ResultLimit = 1439
start := time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 0, 1)
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneMin, start, end)
if !errors.Is(err, kline.ErrRequestExceedsExchangeLimits) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrRequestExceedsExchangeLimits)
}
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.OneHour)
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneHour, start, end)
if !errors.Is(err, kline.ErrValidatingParams) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrValidatingParams)
}
err = b.CurrencyPairs.Store(asset.Spot, &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
Enabled: []currency.Pair{pair},
@@ -2730,7 +2705,19 @@ func TestGetKlineRequest(t *testing.T) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneHour, start, end)
_, err = b.GetKlineRequest(pair, asset.Spot, 0, time.Time{}, time.Time{}, false)
if !errors.Is(err, kline.ErrInvalidInterval) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrInvalidInterval)
}
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneMin, time.Time{}, time.Time{}, false)
if !errors.Is(err, kline.ErrCannotConstructInterval) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrCannotConstructInterval)
}
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.IntervalCapacity{Interval: kline.OneMin})
b.Features.Enabled.Kline.GlobalResultLimit = 1439
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneHour, time.Time{}, time.Time{}, false)
if !errors.Is(err, errAssetRequestFormatIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errAssetRequestFormatIsNil)
}
@@ -2745,7 +2732,65 @@ func TestGetKlineRequest(t *testing.T) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
r, err := b.GetKlineRequest(pair, asset.Spot, kline.OneHour, start, end)
start := time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 0, 1)
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneMin, start, end, true)
if !errors.Is(err, kline.ErrRequestExceedsExchangeLimits) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrRequestExceedsExchangeLimits)
}
_, err = b.GetKlineRequest(pair, asset.Spot, kline.OneMin, start, end, false)
if !errors.Is(err, kline.ErrRequestExceedsExchangeLimits) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrRequestExceedsExchangeLimits)
}
_, err = b.GetKlineRequest(pair, asset.Futures, kline.OneHour, start, end, false)
if !errors.Is(err, kline.ErrValidatingParams) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrValidatingParams)
}
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.IntervalCapacity{Interval: kline.OneHour})
r, err := b.GetKlineRequest(pair, asset.Spot, kline.OneHour, start, end, false)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if r.Exchange != "klineTest" {
t.Fatalf("received: '%v' but expected: '%v'", r.Exchange, "klineTest")
}
if !r.Pair.Equal(pair) {
t.Fatalf("received: '%v' but expected: '%v'", r.Pair, pair)
}
if r.Asset != asset.Spot {
t.Fatalf("received: '%v' but expected: '%v'", r.Asset, asset.Spot)
}
if r.ExchangeInterval != kline.OneHour {
t.Fatalf("received: '%v' but expected: '%v'", r.ExchangeInterval, kline.OneHour)
}
if r.ClientRequired != kline.OneHour {
t.Fatalf("received: '%v' but expected: '%v'", r.ClientRequired, kline.OneHour)
}
if r.Start != start {
t.Fatalf("received: '%v' but expected: '%v'", r.Start, start)
}
if r.End != end {
t.Fatalf("received: '%v' but expected: '%v'", r.End, end)
}
if r.RequestFormatted.String() != "BTCUSDT" {
t.Fatalf("received: '%v' but expected: '%v'", r.RequestFormatted.String(), "BTCUSDT")
}
end = time.Now().Truncate(kline.OneHour.Duration()).UTC()
start = end.Add(-kline.OneHour.Duration() * 1439)
r, err = b.GetKlineRequest(pair, asset.Spot, kline.OneHour, start, end, true)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
@@ -2807,8 +2852,8 @@ func TestGetKlineExtendedRequest(t *testing.T) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrCannotConstructInterval)
}
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.OneMin)
b.Features.Enabled.Kline.ResultLimit = 100
b.Features.Enabled.Kline.Intervals = kline.DeployExchangeIntervals(kline.IntervalCapacity{Interval: kline.OneMin})
b.Features.Enabled.Kline.GlobalResultLimit = 100
start := time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 0, 1)

View File

@@ -115,19 +115,19 @@ func (g *Gateio) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.TwelveHour},
kline.IntervalCapacity{Interval: kline.OneDay},
),
ResultLimit: 1001,
GlobalResultLimit: 1001,
},
},
}
@@ -902,7 +902,7 @@ func (g *Gateio) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (g *Gateio) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := g.GetKlineRequest(pair, a, interval, start, end)
req, err := g.GetKlineRequest(pair, a, interval, start, end, true)
if err != nil {
return nil, err
}

View File

@@ -206,7 +206,7 @@ func (h *HitBTC) GetCandles(ctx context.Context, currencyPair, limit, period str
}
var resp []ChartData
path := fmt.Sprintf("/%s/%s?%s", apiV2Candles, currencyPair, vals.Encode())
path := "/" + apiV2Candles + "/" + currencyPair + "?" + vals.Encode()
return resp, h.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp)
}

View File

@@ -116,7 +116,6 @@ func TestGetHistoricCandlesExtended(t *testing.T) {
}
startTime := time.Unix(1546300800, 0)
end := time.Unix(1577836799, 0)
_, err = h.GetHistoricCandlesExtended(context.Background(), pair, asset.Spot, kline.OneHour, startTime, end)
if err != nil {
t.Fatal(err)
@@ -979,6 +978,7 @@ func TestWsTrades(t *testing.T) {
}
func Test_FormatExchangeKlineInterval(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
interval kline.Interval
@@ -1000,18 +1000,20 @@ func Test_FormatExchangeKlineInterval(t *testing.T) {
"D7",
},
{
"AllOther",
"OneMonth",
kline.OneMonth,
"",
"1M",
},
}
for x := range testCases {
test := testCases[x]
t.Run(test.name, func(t *testing.T) {
ret := h.FormatExchangeKlineInterval(test.interval)
t.Parallel()
ret, err := h.FormatExchangeKlineInterval(test.interval)
if err != nil {
t.Fatal(err)
}
if ret != test.output {
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
}

View File

@@ -116,18 +116,18 @@ func (h *HitBTC) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.FourHour,
kline.OneDay,
kline.SevenDay,
kline.OneMonth,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.SevenDay},
kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 1000,
GlobalResultLimit: 1000,
},
},
}
@@ -850,30 +850,48 @@ func (h *HitBTC) ValidateCredentials(ctx context.Context, assetType asset.Item)
}
// FormatExchangeKlineInterval returns Interval to exchange formatted string
func (h *HitBTC) FormatExchangeKlineInterval(in kline.Interval) string {
func (h *HitBTC) FormatExchangeKlineInterval(in kline.Interval) (string, error) {
switch in {
case kline.OneMin, kline.ThreeMin,
kline.FiveMin, kline.FifteenMin, kline.ThirtyMin:
return "M" + in.Short()[:len(in.Short())-1]
case kline.OneMin:
return "M1", nil
case kline.ThreeMin:
return "M3", nil
case kline.FiveMin:
return "M5", nil
case kline.FifteenMin:
return "M15", nil
case kline.ThirtyMin:
return "M30", nil
case kline.OneHour:
return "H1", nil
case kline.FourHour:
return "H4", nil
case kline.OneDay:
return "D1"
case kline.SevenDay:
return "D7"
return "D1", nil
case kline.OneWeek:
return "D7", nil
case kline.OneMonth:
return "1M", nil
}
return ""
return "", fmt.Errorf("%w %v", kline.ErrInvalidInterval, in)
}
// GetHistoricCandles returns candles between a time period for a set time interval
func (h *HitBTC) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := h.GetKlineRequest(pair, a, interval, start, end)
req, err := h.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
formattedInterval, err := h.FormatExchangeKlineInterval(req.ExchangeInterval)
if err != nil {
return nil, err
}
data, err := h.GetCandles(ctx,
req.RequestFormatted.String(),
strconv.FormatInt(int64(h.Features.Enabled.Kline.ResultLimit), 10),
h.FormatExchangeKlineInterval(req.ExchangeInterval),
strconv.FormatInt(req.RequestLimit, 10),
formattedInterval,
req.Start,
req.End)
if err != nil {
@@ -901,13 +919,18 @@ func (h *HitBTC) GetHistoricCandlesExtended(ctx context.Context, pair currency.P
return nil, err
}
formattedInterval, err := h.FormatExchangeKlineInterval(req.ExchangeInterval)
if err != nil {
return nil, err
}
timeSeries := make([]kline.Candle, 0, req.Size())
for y := range req.RangeHolder.Ranges {
var data []ChartData
data, err = h.GetCandles(ctx,
req.RequestFormatted.String(),
strconv.FormatInt(int64(h.Features.Enabled.Kline.ResultLimit), 10),
h.FormatExchangeKlineInterval(req.ExchangeInterval),
strconv.FormatInt(req.RequestLimit, 10),
formattedInterval,
req.RangeHolder.Ranges[y].Start.Time,
req.RangeHolder.Ranges[y].End.Time)
if err != nil {

View File

@@ -145,18 +145,23 @@ func (h *HUOBI) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.FourHour,
kline.OneDay,
kline.OneWeek,
kline.OneMonth,
kline.OneYear,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.OneYear},
// NOTE: The supported time intervals below are returned
// offset to the Asia/Shanghai time zone. This may lead to
// issues with candle quality and conversion as the
// intervals may be broken up. Therefore the below intervals
// are constructed from hourly candles.
// kline.IntervalCapacity{Interval: kline.OneDay},
// kline.IntervalCapacity{Interval: kline.OneWeek},
// kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 2000,
GlobalResultLimit: 2000,
},
},
}
@@ -1745,7 +1750,7 @@ func (h *HUOBI) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (h *HUOBI) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := h.GetKlineRequest(pair, a, interval, start, end)
req, err := h.GetKlineRequest(pair, a, interval, start, end, true)
if err != nil {
return nil, err
}
@@ -1758,6 +1763,7 @@ func (h *HUOBI) GetHistoricCandles(ctx context.Context, pair currency.Pair, a as
candles, err := h.GetSpotKline(ctx, KlinesRequestParams{
Period: h.FormatExchangeKlineInterval(req.ExchangeInterval),
Symbol: req.Pair,
Size: int(req.RequestLimit),
})
if err != nil {
return nil, err

View File

@@ -163,17 +163,21 @@ func (k *Item) addPadding(start, exclusiveEnd time.Time, purgeOnPartial bool) er
return errCannotEstablishTimeWindow
}
segments := int(window / k.Interval.Duration())
if segments == len(k.Candles) {
return nil
}
padded := make([]Candle, segments)
padded := make([]Candle, int(window/k.Interval.Duration()))
var target int
for x := range padded {
if target >= len(k.Candles) || !k.Candles[target].Time.Equal(start) {
switch {
case target >= len(k.Candles):
padded[x].Time = start
} else {
case !k.Candles[target].Time.Equal(start):
if k.Candles[target].Time.Before(start) {
return fmt.Errorf("%w when it should be %s truncated at a %s interval",
errCandleOpenTimeIsNotUTCAligned,
start.Add(k.Interval.Duration()),
k.Interval)
}
padded[x].Time = start
default:
padded[x] = k.Candles[target]
target++
}
@@ -183,8 +187,15 @@ func (k *Item) addPadding(start, exclusiveEnd time.Time, purgeOnPartial bool) er
// NOTE: This checks if the end time exceeds time.Now() and we are capturing
// a partially created candle. This will only delete an element if it is
// empty.
if purgeOnPartial && padded[len(padded)-1].Volume == 0 {
padded = padded[:len(padded)-1]
if purgeOnPartial {
lastElement := padded[len(padded)-1]
if lastElement.Volume == 0 &&
lastElement.Open == 0 &&
lastElement.High == 0 &&
lastElement.Low == 0 &&
lastElement.Close == 0 {
padded = padded[:len(padded)-1]
}
}
k.Candles = padded
return nil
@@ -349,29 +360,38 @@ func (k *Item) ConvertToNewInterval(newInterval Interval) (*Item, error) {
if len(candles) == 0 {
return nil, fmt.Errorf("%w to %v no candle data", ErrInsufficientCandleData, newInterval)
}
var target int
for x := range k.Candles {
if candles[target].Time.IsZero() {
candles[target].Time = k.Candles[x].Time
}
// If this check does not pass, this candle has zero values or is padding.
// It has nothing to apply to the new interval candle as it will distort
// candle data.
if k.Candles[x].Open != 0 &&
k.Candles[x].High != 0 &&
k.Candles[x].Low != 0 &&
k.Candles[x].Close != 0 &&
k.Candles[x].Volume != 0 {
if candles[target].Time.IsZero() {
candles[target].Time = k.Candles[x].Time
}
if candles[target].Open == 0 {
candles[target].Open = k.Candles[x].Open
}
if candles[target].Open == 0 {
candles[target].Open = k.Candles[x].Open
}
if k.Candles[x].High > candles[target].High {
candles[target].High = k.Candles[x].High
}
if k.Candles[x].High > candles[target].High {
candles[target].High = k.Candles[x].High
}
if candles[target].Low == 0 || k.Candles[x].Low < candles[target].Low {
candles[target].Low = k.Candles[x].Low
}
if candles[target].Low == 0 || k.Candles[x].Low < candles[target].Low {
candles[target].Low = k.Candles[x].Low
}
candles[target].Volume += k.Candles[x].Volume
candles[target].Volume += k.Candles[x].Volume
candles[target].Close = k.Candles[x].Close
}
if (x+1)%oldIntervalsPerNewCandle == 0 {
candles[target].Close = k.Candles[x].Close
target++
// Note: Below checks the length of the proceeding slice so we can
// break instantly if we cannot make an entire candle. e.g. 60 min
// candles in an hour candle and we have 59 minute candles left.
@@ -379,6 +399,7 @@ func (k *Item) ConvertToNewInterval(newInterval Interval) (*Item, error) {
if len(k.Candles[x:])-1 < oldIntervalsPerNewCandle {
break
}
target++
}
}
return &Item{
@@ -582,12 +603,12 @@ func (k *Item) EqualSource(i *Item) error {
// DeployExchangeIntervals aligns and stores supported intervals for an exchange
// for future matching.
func DeployExchangeIntervals(enabled ...Interval) ExchangeIntervals {
sort.Slice(enabled, func(i, j int) bool { return enabled[i] < enabled[j] })
func DeployExchangeIntervals(enabled ...IntervalCapacity) ExchangeIntervals {
sort.Slice(enabled, func(i, j int) bool { return enabled[i].Interval < enabled[j].Interval })
supported := make(map[Interval]bool)
supported := make(map[Interval]int64)
for x := range enabled {
supported[enabled[x]] = true
supported[enabled[x].Interval] = enabled[x].Capacity
}
return ExchangeIntervals{supported: supported, aligned: enabled}
}
@@ -596,7 +617,8 @@ func DeployExchangeIntervals(enabled ...Interval) ExchangeIntervals {
// future this might be able to be deprecated because we can construct custom
// intervals from the supported list.
func (e *ExchangeIntervals) ExchangeSupported(in Interval) bool {
return e.supported[in]
_, ok := e.supported[in]
return ok
}
// Construct fetches supported interval that can construct the required interval
@@ -606,17 +628,41 @@ func (e *ExchangeIntervals) Construct(required Interval) (Interval, error) {
return 0, ErrInvalidInterval
}
if e.supported[required] {
if _, ok := e.supported[required]; ok {
// Directly supported by exchange can return.
return required, nil
}
for x := len(e.aligned) - 1; x > -1; x-- {
if e.aligned[x] < required && required%e.aligned[x] == 0 {
if e.aligned[x].Interval < required && required%e.aligned[x].Interval == 0 {
// Indirectly supported by exchange. Can generate required candle
// from this lower time frame supported candle.
return e.aligned[x], nil
return e.aligned[x].Interval, nil
}
}
return 0, ErrCannotConstructInterval
}
// GetIntervalResultLimit returns the maximum amount of candles that can be
// returned for a specific interval. If the individual interval limit is not set,
// it will be ignored and the global result limit will be returned.
func (e *ExchangeCapabilitiesEnabled) GetIntervalResultLimit(interval Interval) (int64, error) {
if e == nil {
return 0, errExchangeCapabilitiesEnabledIsNil
}
val, ok := e.Intervals.supported[interval]
if !ok {
return 0, fmt.Errorf("[%s] %w", interval, errIntervalNotSupported)
}
if val > 0 {
return val, nil
}
if e.GlobalResultLimit == 0 {
return 0, fmt.Errorf("%w there is no global result limit set", errCannotFetchIntervalLimit)
}
return int64(e.GlobalResultLimit), nil
}

View File

@@ -1033,6 +1033,18 @@ func TestConvertToNewInterval(t *testing.T) {
Close: 5555,
Volume: 2520,
},
{
Time: tn.AddDate(0, 0, 6),
// Empty end padding
},
{
Time: tn.AddDate(0, 0, 7),
// Empty end padding
},
{
Time: tn.AddDate(0, 0, 8),
// Empty end padding
},
}
_, err = old.ConvertToNewInterval(newInterval)
@@ -1040,7 +1052,7 @@ func TestConvertToNewInterval(t *testing.T) {
t.Errorf("received '%v' expected '%v'", err, errCandleDataNotPadded)
}
err = old.addPadding(tn, tn.AddDate(0, 0, 6), false)
err = old.addPadding(tn, tn.AddDate(0, 0, 9), false)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
@@ -1050,8 +1062,8 @@ func TestConvertToNewInterval(t *testing.T) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(newCandle.Candles) != 2 {
t.Errorf("received '%v' expected '%v'", len(newCandle.Candles), 2)
if len(newCandle.Candles) != 3 {
t.Errorf("received '%v' expected '%v'", len(newCandle.Candles), 3)
}
}
@@ -1106,6 +1118,37 @@ func TestAddPadding(t *testing.T) {
t.Fatalf("received '%v' expected '%v'", err, errCannotEstablishTimeWindow)
}
k.Candles = []Candle{
{
Time: tn.Add(time.Hour * 8),
Open: 1337,
High: 1339,
Low: 1336,
Close: 1338,
Volume: 1337,
},
{
Time: tn.AddDate(0, 0, 1).Add(time.Hour * 8),
Open: 1338,
High: 2000,
Low: 1332,
Close: 1696,
Volume: 6420,
},
{
Time: tn.AddDate(0, 0, 2).Add(time.Hour * 8),
Open: 1696,
High: 1998,
Low: 1337,
Close: 6969,
Volume: 2520,
}}
err = k.addPadding(tn, tn.AddDate(0, 0, 3), false)
if !errors.Is(err, errCandleOpenTimeIsNotUTCAligned) {
t.Fatalf("received '%v' expected '%v'", err, errCandleOpenTimeIsNotUTCAligned)
}
k.Candles = []Candle{
{
Time: tn,
@@ -1207,7 +1250,7 @@ func TestDeployExchangeIntervals(t *testing.T) {
t.Errorf("received '%v' expected '%v'", exchangeIntervals.ExchangeSupported(OneWeek), false)
}
exchangeIntervals = DeployExchangeIntervals(OneWeek)
exchangeIntervals = DeployExchangeIntervals(IntervalCapacity{Interval: OneWeek})
if !exchangeIntervals.ExchangeSupported(OneWeek) {
t.Errorf("received '%v' expected '%v'", exchangeIntervals.ExchangeSupported(OneWeek), true)
}
@@ -1231,7 +1274,7 @@ func TestDeployExchangeIntervals(t *testing.T) {
t.Errorf("received '%v' expected '%v'", request, OneWeek)
}
exchangeIntervals = DeployExchangeIntervals(OneWeek, OneDay)
exchangeIntervals = DeployExchangeIntervals(IntervalCapacity{Interval: OneWeek}, IntervalCapacity{Interval: OneDay})
request, err = exchangeIntervals.Construct(OneMonth)
if !errors.Is(err, nil) {
@@ -1286,3 +1329,51 @@ func TestSetHasDataFromCandles(t *testing.T) {
t.Errorf("received '%v' expected '%v'", true, false)
}
}
func TestGetIntervalResultLimit(t *testing.T) {
t.Parallel()
var e *ExchangeCapabilitiesEnabled
_, err := e.GetIntervalResultLimit(OneMin)
if !errors.Is(err, errExchangeCapabilitiesEnabledIsNil) {
t.Errorf("received '%v' expected '%v'", err, errExchangeCapabilitiesEnabledIsNil)
}
e = &ExchangeCapabilitiesEnabled{}
e.Intervals = ExchangeIntervals{}
_, err = e.GetIntervalResultLimit(OneDay)
if !errors.Is(err, errIntervalNotSupported) {
t.Errorf("received '%v' expected '%v'", err, errIntervalNotSupported)
}
e.Intervals = ExchangeIntervals{
supported: map[Interval]int64{
OneDay: 100000,
OneMin: 0,
},
}
_, err = e.GetIntervalResultLimit(OneMin)
if !errors.Is(err, errCannotFetchIntervalLimit) {
t.Errorf("received '%v' expected '%v'", err, errCannotFetchIntervalLimit)
}
limit, err := e.GetIntervalResultLimit(OneDay)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if limit != 100000 {
t.Errorf("received '%v' expected '%v'", limit, 100000)
}
e.GlobalResultLimit = 1337
limit, err = e.GetIntervalResultLimit(OneMin)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if limit != 1337 {
t.Errorf("received '%v' expected '%v'", limit, 1337)
}
}

View File

@@ -71,10 +71,14 @@ var (
// back further than what is allowed.
ErrRequestExceedsMaxLookback = errors.New("the requested time window exceeds the maximum lookback period available in the historical data, please reduce window between start and end date of your request")
errInsufficientTradeData = errors.New("insufficient trade data")
errCandleDataNotPadded = errors.New("candle data not padded")
errCannotEstablishTimeWindow = errors.New("cannot establish time window")
errNilKline = errors.New("kline item is nil")
errInsufficientTradeData = errors.New("insufficient trade data")
errCandleDataNotPadded = errors.New("candle data not padded")
errCannotEstablishTimeWindow = errors.New("cannot establish time window")
errNilKline = errors.New("kline item is nil")
errExchangeCapabilitiesEnabledIsNil = errors.New("exchange capabilities enabled is nil")
errCannotFetchIntervalLimit = errors.New("cannot fetch interval limit")
errIntervalNotSupported = errors.New("interval not supported")
errCandleOpenTimeIsNotUTCAligned = errors.New("candle open time is not UTC aligned")
oneYearDurationInNano = float64(OneYear.Duration().Nanoseconds())
@@ -138,15 +142,20 @@ type ExchangeCapabilitiesSupported struct {
// ExchangeCapabilitiesEnabled all kline related exchange enabled options
type ExchangeCapabilitiesEnabled struct {
Intervals ExchangeIntervals
ResultLimit uint32
// Intervals defines whether the exchange supports interval kline requests.
Intervals ExchangeIntervals
// GlobalResultLimit is the maximum amount of candles that can be returned
// across all intervals. This is used to determine if a request will exceed
// the exchange limits. Indivudal interval limits are stored in the
// ExchangeIntervals struct. If this is set to 0, it will be ignored.
GlobalResultLimit uint32
}
// ExchangeIntervals stores the supported intervals in an optimized lookup table
// with a supplementary aligned retrieval list
type ExchangeIntervals struct {
supported map[Interval]bool
aligned []Interval
supported map[Interval]int64
aligned []IntervalCapacity
}
// Interval type for kline Interval usage
@@ -183,3 +192,9 @@ type IntervalTime struct {
Time time.Time
Ticks int64
}
// IntervalCapacity is used to store the interval and capacity for a candle return
type IntervalCapacity struct {
Interval Interval
Capacity int64
}

View File

@@ -13,9 +13,10 @@ import (
var (
// ErrUnsetName is an error for when the exchange name is not set
ErrUnsetName = errors.New("unset exchange name")
errNilRequest = errors.New("nil kline request")
errNoTimeSeriesDataToConvert = errors.New("no time series data to convert")
ErrUnsetName = errors.New("unset exchange name")
errNilRequest = errors.New("nil kline request")
errNoTimeSeriesDataToConvert = errors.New("no time series data to convert")
errInvalidSpecificEndpointLimit = errors.New("specific endpoint limit must be greater than 0")
// PartialCandle is string flag for when the most recent candle is partially
// formed.
@@ -53,11 +54,14 @@ type Request struct {
// ProcessedCandles stores the candles that have been processed, but not converted
// to the ClientRequiredInterval
ProcessedCandles []Candle
// RequestLimit is the potential maximum amount of candles that can be
// returned
RequestLimit int64
}
// CreateKlineRequest generates a `Request` type for interval conversions
// supported by an exchange.
func CreateKlineRequest(name string, pair, formatted currency.Pair, a asset.Item, clientRequired, exchangeInterval Interval, start, end time.Time) (*Request, error) {
func CreateKlineRequest(name string, pair, formatted currency.Pair, a asset.Item, clientRequired, exchangeInterval Interval, start, end time.Time, specificEndpointLimit int64) (*Request, error) {
if name == "" {
return nil, ErrUnsetName
}
@@ -81,6 +85,10 @@ func CreateKlineRequest(name string, pair, formatted currency.Pair, a asset.Item
return nil, err
}
if specificEndpointLimit <= 0 {
return nil, errInvalidSpecificEndpointLimit
}
// Force UTC alignment
start = start.UTC()
end = end.UTC()
@@ -116,6 +124,7 @@ func CreateKlineRequest(name string, pair, formatted currency.Pair, a asset.Item
Start: start,
End: end,
PartialCandle: end.After(time.Now()),
RequestLimit: specificEndpointLimit,
}, nil
}

View File

@@ -13,51 +13,56 @@ import (
func TestCreateKlineRequest(t *testing.T) {
t.Parallel()
_, err := CreateKlineRequest("", currency.EMPTYPAIR, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{})
_, err := CreateKlineRequest("", currency.EMPTYPAIR, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, ErrUnsetName) {
t.Fatalf("received: '%v', but expected '%v'", err, ErrUnsetName)
}
_, err = CreateKlineRequest("name", currency.EMPTYPAIR, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{})
_, err = CreateKlineRequest("name", currency.EMPTYPAIR, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v', but expected '%v'", err, currency.ErrCurrencyPairEmpty)
}
pair := currency.NewPair(currency.BTC, currency.USDT)
_, err = CreateKlineRequest("name", pair, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{})
_, err = CreateKlineRequest("name", pair, currency.EMPTYPAIR, 0, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v', but expected '%v'", err, currency.ErrCurrencyPairEmpty)
}
pair2 := pair.Upper()
_, err = CreateKlineRequest("name", pair, pair2, 0, 0, 0, time.Time{}, time.Time{})
_, err = CreateKlineRequest("name", pair, pair2, 0, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v', but expected '%v'", err, asset.ErrNotSupported)
}
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, 0, 0, time.Time{}, time.Time{})
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, 0, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, ErrInvalidInterval) {
t.Fatalf("received: '%v', but expected '%v'", err, ErrInvalidInterval)
}
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, 0, time.Time{}, time.Time{})
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, 0, time.Time{}, time.Time{}, 0)
if !errors.Is(err, ErrInvalidInterval) {
t.Fatalf("received: '%v', but expected '%v'", err, ErrInvalidInterval)
}
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, time.Time{}, time.Time{})
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, time.Time{}, time.Time{}, 0)
if !errors.Is(err, common.ErrDateUnset) {
t.Fatalf("received: '%v', but expected '%v'", err, common.ErrDateUnset)
}
start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, time.Time{})
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, time.Time{}, 0)
if !errors.Is(err, common.ErrDateUnset) {
t.Fatalf("received: '%v', but expected '%v'", err, common.ErrDateUnset)
}
end := start.AddDate(0, 0, 1)
r, err := CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, end)
_, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, end, 0)
if !errors.Is(err, errInvalidSpecificEndpointLimit) {
t.Fatalf("received: '%v', but expected '%v'", err, errInvalidSpecificEndpointLimit)
}
r, err := CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
@@ -98,7 +103,7 @@ func TestCreateKlineRequest(t *testing.T) {
// aligned correctly.
end = end.Round(0)
end = end.Add(time.Second * 30)
r, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, end)
r, err = CreateKlineRequest("name", pair, pair2, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
@@ -121,7 +126,7 @@ func TestGetRanges(t *testing.T) {
t.Fatalf("received: '%v', but expected '%v'", err, errNilRequest)
}
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end)
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
@@ -207,8 +212,13 @@ func TestRequest_ProcessResponse(t *testing.T) {
t.Fatalf("received: '%v', but expected '%v'", err, errNoTimeSeriesDataToConvert)
}
_, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneHour, start, end, 0)
if !errors.Is(err, errInvalidSpecificEndpointLimit) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
// no conversion
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneHour, start, end)
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneHour, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
@@ -223,7 +233,7 @@ func TestRequest_ProcessResponse(t *testing.T) {
}
// with conversion
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end)
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
@@ -240,7 +250,7 @@ func TestRequest_ProcessResponse(t *testing.T) {
// Potential partial candle
end = time.Now().UTC()
start = end.AddDate(0, 0, -5).Truncate(time.Duration(OneDay))
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneDay, OneDay, start, end)
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneDay, OneDay, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
@@ -285,7 +295,7 @@ func TestRequest_ProcessResponse(t *testing.T) {
}
// end date far into the dark depths of future reality
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneDay, OneDay, start, end.AddDate(1, 0, 0))
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneDay, OneDay, start, end.AddDate(1, 0, 0), 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
@@ -338,7 +348,7 @@ func TestExtendedRequest_ProcessResponse(t *testing.T) {
}
// no conversion
r, err := CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneHour, start, end)
r, err := CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneHour, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}
@@ -361,7 +371,7 @@ func TestExtendedRequest_ProcessResponse(t *testing.T) {
// with conversion
ohc = getOneMinute()
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end)
r, err = CreateKlineRequest("name", pair, pair, asset.Spot, OneHour, OneMin, start, end, 1)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v', but expected '%v'", err, nil)
}

View File

@@ -158,17 +158,17 @@ func (k *Kraken) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.FourHour,
kline.OneDay,
kline.OneWeek,
kline.FifteenDay,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.OneWeek},
kline.IntervalCapacity{Interval: kline.FifteenDay},
),
ResultLimit: 720,
GlobalResultLimit: 720,
},
},
}
@@ -1472,7 +1472,7 @@ func (k *Kraken) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (k *Kraken) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := k.GetKlineRequest(pair, a, interval, start, end)
req, err := k.GetKlineRequest(pair, a, interval, start, end, true)
if err != nil {
return nil, err
}

View File

@@ -93,19 +93,25 @@ func (l *Lbank) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.FourHour,
kline.EightHour,
kline.TwelveHour,
kline.OneDay,
kline.OneWeek,
kline.OneMonth,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.FourHour},
// NOTE: The supported time intervals below are returned
// offset to the Asia/HongKong time zone. This may lead to
// issues with candle quality and conversion as the
// intervals may be broken up. Therefore the below intervals
// are constructed from hourly -> 4 hourly candles.
// kline.IntervalCapacity{Interval: kline.EightHour}, // The docs suggest this is supported but it isn't.
// kline.IntervalCapacity{Interval: kline.TwelveHour}, // The docs suggest this is supported but it isn't.
// kline.IntervalCapacity{Interval: kline.OneDay},
// kline.IntervalCapacity{Interval: kline.OneWeek},
// kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 2000,
GlobalResultLimit: 2000,
},
},
}
@@ -889,14 +895,14 @@ func (l *Lbank) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (l *Lbank) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := l.GetKlineRequest(pair, a, interval, start, end)
req, err := l.GetKlineRequest(pair, a, interval, start, end, true)
if err != nil {
return nil, err
}
data, err := l.GetKlines(ctx,
req.RequestFormatted.String(),
strconv.FormatInt(int64(l.Features.Enabled.Kline.ResultLimit), 10),
strconv.FormatInt(req.RequestLimit, 10),
l.FormatExchangeKlineInterval(req.ExchangeInterval),
strconv.FormatInt(req.Start.Unix(), 10))
if err != nil {
@@ -929,7 +935,7 @@ func (l *Lbank) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pa
var data []KlineResponse
data, err = l.GetKlines(ctx,
req.RequestFormatted.String(),
strconv.FormatInt(int64(l.Features.Enabled.Kline.ResultLimit), 10),
strconv.FormatInt(req.RequestLimit, 10),
l.FormatExchangeKlineInterval(req.ExchangeInterval),
strconv.FormatInt(req.RangeHolder.Ranges[x].Start.Ticks, 10))
if err != nil {

View File

@@ -419,26 +419,24 @@ func (o *OKCoin) GetMarketData(ctx context.Context, request *GetMarketDataReques
if !ok {
return nil, common.GetAssertError("string", t[0])
}
var tempCandle kline.Candle
if tempCandle.Time, err = time.Parse(time.RFC3339, v); err != nil {
if candles[x].Time, err = time.Parse(time.RFC3339, v); err != nil {
return nil, err
}
if tempCandle.Open, err = convert.FloatFromString(t[1]); err != nil {
if candles[x].Open, err = convert.FloatFromString(t[1]); err != nil {
return nil, err
}
if tempCandle.High, err = convert.FloatFromString(t[2]); err != nil {
if candles[x].High, err = convert.FloatFromString(t[2]); err != nil {
return nil, err
}
if tempCandle.Low, err = convert.FloatFromString(t[3]); err != nil {
if candles[x].Low, err = convert.FloatFromString(t[3]); err != nil {
return nil, err
}
if tempCandle.Close, err = convert.FloatFromString(t[4]); err != nil {
if candles[x].Close, err = convert.FloatFromString(t[4]); err != nil {
return nil, err
}
if tempCandle.Volume, err = convert.FloatFromString(t[5]); err != nil {
if candles[x].Volume, err = convert.FloatFromString(t[5]); err != nil {
return nil, err
}
candles[x] = tempCandle
}
return candles, nil
}

View File

@@ -1277,8 +1277,8 @@ func TestGetHistoricCandles(t *testing.T) {
t.Fatal(err)
}
startTime := time.Unix(1588636800, 0)
_, err = o.GetHistoricCandles(context.Background(),
pair, asset.Spot, kline.OneDay, startTime, time.Now())
endTime := startTime.Add(time.Hour * 24 * 7)
_, err = o.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, startTime, endTime)
if err != nil {
t.Fatal(err)
}
@@ -1292,8 +1292,7 @@ func TestGetHistoricCandlesExtended(t *testing.T) {
}
startTime := time.Unix(1588636800, 0)
_, err = o.GetHistoricCandlesExtended(context.Background(),
pair, asset.Spot, kline.OneWeek, startTime, time.Now())
_, err = o.GetHistoricCandlesExtended(context.Background(), pair, asset.Spot, kline.OneWeek, startTime, time.Now())
if err != nil {
t.Fatal(err)
}

View File

@@ -119,21 +119,26 @@ func (o *OKCoin) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.ThreeDay,
kline.OneWeek,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
// NOTE: The supported time intervals below are returned
// offset to the Asia/Shanghai time zone. This may lead to
// issues with candle quality and conversion as the
// intervals may be broken up. Therefore the below intervals
// are constructed from hourly candles.
// kline.IntervalCapacity{Interval: kline.SixHour},
// kline.IntervalCapacity{Interval: kline.TwelveHour},
// kline.IntervalCapacity{Interval: kline.OneDay},
// kline.IntervalCapacity{Interval: kline.ThreeDay},
// kline.IntervalCapacity{Interval: kline.OneWeek},
),
ResultLimit: 1440,
GlobalResultLimit: 1440,
},
},
}
@@ -1021,7 +1026,7 @@ func (o *OKCoin) GetHistoricTrades(_ context.Context, _ currency.Pair, _ asset.I
// GetHistoricCandles returns candles between a time period for a set time interval
func (o *OKCoin) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := o.GetKlineRequest(pair, a, interval, start, end)
req, err := o.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}

View File

@@ -2303,7 +2303,7 @@ func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
pair := currency.NewPair(currency.BTC, currency.USDT)
startTime := time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)
endTime := time.Date(2021, 9, 15, 0, 0, 0, 0, time.UTC)
endTime := startTime.AddDate(0, 0, 100)
_, err := ok.GetHistoricCandles(context.Background(), pair, asset.Spot, kline.OneDay, startTime, endTime)
if err != nil {
t.Fatal(err)

View File

@@ -131,27 +131,27 @@ func (ok *Okx) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.TwoDay,
kline.ThreeDay,
kline.FiveDay,
kline.OneWeek,
kline.OneMonth,
kline.ThreeMonth,
kline.SixMonth,
kline.OneYear,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.TwelveHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.TwoDay},
kline.IntervalCapacity{Interval: kline.ThreeDay},
kline.IntervalCapacity{Interval: kline.FiveDay},
kline.IntervalCapacity{Interval: kline.OneWeek},
kline.IntervalCapacity{Interval: kline.OneMonth},
kline.IntervalCapacity{Interval: kline.ThreeMonth},
kline.IntervalCapacity{Interval: kline.SixMonth},
kline.IntervalCapacity{Interval: kline.OneYear},
),
ResultLimit: 300,
GlobalResultLimit: 100, // Reference: https://www.okx.com/docs-v5/en/#rest-api-market-data-get-candlesticks-history
},
},
}
@@ -363,8 +363,7 @@ func (ok *Okx) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item)
if err != nil {
return nil, err
}
var baseVolume float64
var quoteVolume float64
var baseVolume, quoteVolume float64
switch a {
case asset.Spot, asset.Margin:
baseVolume = mdata.Vol24H.Float64()
@@ -1365,7 +1364,7 @@ func (ok *Okx) ValidateCredentials(ctx context.Context, assetType asset.Item) er
// GetHistoricCandles returns candles between a time period for a set time interval
func (ok *Okx) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := ok.GetKlineRequest(pair, a, interval, start, end)
req, err := ok.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}

View File

@@ -123,22 +123,22 @@ func (p *Poloniex) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.FiveMin,
kline.TenMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.ThreeDay,
kline.OneWeek,
kline.OneMonth,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.TenMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
kline.IntervalCapacity{Interval: kline.SixHour},
kline.IntervalCapacity{Interval: kline.TwelveHour},
kline.IntervalCapacity{Interval: kline.OneDay},
kline.IntervalCapacity{Interval: kline.ThreeDay},
kline.IntervalCapacity{Interval: kline.OneWeek},
kline.IntervalCapacity{Interval: kline.OneMonth},
),
ResultLimit: 500,
GlobalResultLimit: 500,
},
},
}
@@ -950,7 +950,7 @@ func (p *Poloniex) ValidateCredentials(ctx context.Context, assetType asset.Item
// GetHistoricCandles returns candles between a time period for a set time interval
func (p *Poloniex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := p.GetKlineRequest(pair, a, interval, start, end)
req, err := p.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}

View File

@@ -867,23 +867,32 @@ func TestWsCreateSubUserResponse(t *testing.T) {
}
func TestGetSpotKline(t *testing.T) {
t.Parallel()
limit, err := z.Features.Enabled.Kline.GetIntervalResultLimit(kline.OneMin)
if err != nil {
t.Fatal(err)
}
arg := KlinesRequestParams{
Symbol: testCurrency,
Type: kline.OneMin.Short() + "in",
Size: int64(z.Features.Enabled.Kline.ResultLimit),
Size: limit,
}
if mockTests {
startTime := time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC)
arg.Since = startTime.UnixMilli()
arg.Type = "1day"
}
_, err := z.GetSpotKline(context.Background(), arg)
_, err = z.GetSpotKline(context.Background(), arg)
if err != nil {
t.Errorf("ZB GetSpotKline: %s", err)
}
}
func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
if mockTests {
t.Skip("mock testing is not supported for this function")
}
currencyPair, err := currency.NewPairFromString(testCurrency)
if err != nil {
t.Fatal(err)
@@ -891,20 +900,14 @@ func TestGetHistoricCandles(t *testing.T) {
startTime := time.Now().Add(-time.Hour * 24)
endTime := time.Now()
if mockTests {
startTime = time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC)
endTime = time.Date(2020, 9, 2, 0, 0, 0, 0, time.UTC)
}
// Current endpoint is dead.
_, err = z.GetHistoricCandles(context.Background(),
currencyPair, asset.Spot, kline.OneDay, startTime, endTime)
_, err = z.GetHistoricCandles(context.Background(), currencyPair, asset.Spot, kline.OneDay, startTime, endTime)
if err != nil {
t.Fatal(err)
}
}
func TestGetHistoricCandlesExtended(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString(testCurrency)
if err != nil {
t.Fatal(err)
@@ -920,8 +923,7 @@ func TestGetHistoricCandlesExtended(t *testing.T) {
startTime = time.Now().Add(-time.Hour * 24 * 365)
endTime = time.Now()
if mockTests {
startTime = time.UnixMilli(1674489600000)
endTime = startTime.Add(kline.OneDay.Duration())
t.Skip("mock testing is not supported for this function")
}
_, err = z.GetHistoricCandlesExtended(context.Background(),
currencyPair, asset.Spot, kline.OneDay, startTime, endTime)

View File

@@ -111,21 +111,26 @@ func (z *ZB) SetDefaults() {
AutoPairUpdates: true,
Kline: kline.ExchangeCapabilitiesEnabled{
Intervals: kline.DeployExchangeIntervals(
kline.OneMin,
kline.ThreeMin,
kline.FiveMin,
kline.FifteenMin,
kline.ThirtyMin,
kline.OneHour,
kline.TwoHour,
kline.FourHour,
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.ThreeDay,
kline.OneWeek,
kline.IntervalCapacity{Interval: kline.OneMin},
kline.IntervalCapacity{Interval: kline.ThreeMin},
kline.IntervalCapacity{Interval: kline.FiveMin},
kline.IntervalCapacity{Interval: kline.FifteenMin},
kline.IntervalCapacity{Interval: kline.ThirtyMin},
kline.IntervalCapacity{Interval: kline.OneHour},
kline.IntervalCapacity{Interval: kline.TwoHour},
kline.IntervalCapacity{Interval: kline.FourHour},
// NOTE: The supported time intervals below are returned
// offset to the Asia/Shanghai time zone. This may lead to
// issues with candle quality and conversion as the
// intervals may be broken up. Therefore the below intervals
// are constructed from hourly candles.
// kline.IntervalCapacity{Interval: kline.SixHour},
// kline.IntervalCapacity{Interval: kline.TwelveHour},
// kline.IntervalCapacity{Interval: kline.OneDay},
// kline.IntervalCapacity{Interval: kline.ThreeDay},
// kline.IntervalCapacity{Interval: kline.OneWeek},
),
ResultLimit: 1000,
GlobalResultLimit: 1000,
},
},
}
@@ -885,7 +890,7 @@ func (z *ZB) FormatExchangeKlineInterval(in kline.Interval) string {
// GetHistoricCandles returns candles between a time period for a set time interval
func (z *ZB) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
req, err := z.GetKlineRequest(pair, a, interval, start, end)
req, err := z.GetKlineRequest(pair, a, interval, start, end, true)
if err != nil {
return nil, err
}
@@ -894,7 +899,7 @@ func (z *ZB) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset
Type: z.FormatExchangeKlineInterval(req.ExchangeInterval),
Symbol: req.RequestFormatted.String(),
Since: start.UnixMilli(),
Size: int64(z.Features.Enabled.Kline.ResultLimit),
Size: req.RequestLimit,
})
if err != nil {
return nil, err