mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-23 07:26:47 +00:00
Feature: Data history manager engine subsystem (#693)
* Adds lovely initial concept for historical data doer
* Adds ability to save tasks. Adds config. Adds startStop to engine
* Has a database microservice without use of globals! Further infrastructure design. Adds readme
* Commentary to help design
* Adds migrations for database
* readme and adds database models
* Some modelling that doesn't work end of day
* Completes datahistoryjob sql.Begins datahistoryjobresult
* Adds datahistoryjob functions to retreive job results. Adapts subsystem
* Adds process for upserting jobs and job results to the database
* Broken end of day weird sqlboiler crap
* Fixes issue with SQL generation.
* RPC generation and addition of basic upsert command
* Renames types
* Adds rpc functions
* quick commit before context swithc. Exchanges aren't being populated
* Begin the tests!
* complete sql tests. stop failed jobs. CLI command creation
* Defines rpc commands
* Fleshes out RPC implementation
* Expands testing
* Expands testing, removes double remove
* Adds coverage of data history subsystem, expands errors and nil checks
* Minor logic improvement
* streamlines datahistory test setup
* End of day minor linting
* Lint, convert simplify, rpc expansion, type expansion, readme expansion
* Documentation update
* Renames for consistency
* Completes RPC server commands
* Fixes tests
* Speeds up testing by reducing unnecessary actions. Adds maxjobspercycle config
* Comments for everything
* Adds missing result string. checks interval supported. default start end cli
* Fixes ID problem. Improves binance trade fetch. job ranges are processed
* adds dbservice coverage. adds rpcserver coverage
* docs regen, uses dbcon interface, reverts binance, fixes races, toggle manager
* Speed up tests, remove bad global usage, fix uuid check
* Adds verbose. Updates docs. Fixes postgres
* Minor changes to logging and start stop
* Fixes postgres db tests, fixes postgres column typo
* Fixes old string typo,removes constraint,error parsing for nonreaders
* prevents dhm running when table doesn't exist. Adds prereq documentation
* Adds parallel, rmlines, err fix, comment fix, minor param fixes
* doc regen, common time range check and test updating
* Fixes job validation issues. Updates candle range checker.
* Ensures test cannot fail due to time.Now() shenanigans
* Fixes oopsie, adds documentation and a warn
* Fixes another time test, adjusts copy
* Drastically speeds up data history manager tests via function overrides
* Fixes summary bug and better logs
* Fixes local time test, fixes websocket tests
* removes defaults and comment,updates error messages,sets cli command args
* Fixes FTX trade processing
* Fixes issue where jobs got stuck if data wasn't returned but retrieval was successful
* Improves test speed. Simplifies trade verification SQL. Adds command help
* Fixes the oopsies
* Fixes use of query within transaction. Fixes trade err
* oopsie, not needed
* Adds missing data status. Properly ends job even when data is missing
* errors are more verbose and so have more words to describe them
* Doc regen for new status
* tiny test tinkering
* str := string("Removes .String()").String()
* Merge fixups
* Fixes a data race discovered during github actions
* Allows websocket test to pass consistently
* Fixes merge issue preventing datahistorymanager from starting via config
* Niterinos cmd defaults and explanations
* fixes default oopsie
* Fixes lack of nil protection
* Additional oopsie
* More detailed error for validating job exchange
This commit is contained in:
@@ -661,6 +661,7 @@ func (b *Binance) GetAccount() (*Account, error) {
|
||||
return &resp.Account, nil
|
||||
}
|
||||
|
||||
// GetMarginAccount returns account information for margin accounts
|
||||
func (b *Binance) GetMarginAccount() (*MarginAccount, error) {
|
||||
var resp MarginAccount
|
||||
params := url.Values{}
|
||||
@@ -688,6 +689,8 @@ func (b *Binance) SendHTTPRequest(ePath exchange.URL, path string, f request.End
|
||||
Endpoint: f})
|
||||
}
|
||||
|
||||
// SendAPIKeyHTTPRequest is a special API request where the api key is
|
||||
// appended to the headers without a secret
|
||||
func (b *Binance) SendAPIKeyHTTPRequest(ePath exchange.URL, path string, f request.EndpointLimit, result interface{}) error {
|
||||
endpointPath, err := b.API.Endpoints.GetURL(ePath)
|
||||
if err != nil {
|
||||
|
||||
@@ -1522,7 +1522,11 @@ func (b *Binance) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, s
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
var candles []CandleStick
|
||||
for x := range dates.Ranges {
|
||||
req := KlinesRequestParams{
|
||||
Interval: b.FormatExchangeKlineInterval(interval),
|
||||
@@ -1532,7 +1536,7 @@ func (b *Binance) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, s
|
||||
Limit: int(b.Features.Enabled.Kline.ResultLimit),
|
||||
}
|
||||
|
||||
candles, err := b.GetSpotKline(&req)
|
||||
candles, err = b.GetSpotKline(&req)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
@@ -1554,11 +1558,11 @@ func (b *Binance) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, s
|
||||
}
|
||||
}
|
||||
|
||||
err := dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", b.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary)
|
||||
}
|
||||
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
ret.SortCandlesByTimestamp(false)
|
||||
|
||||
@@ -532,8 +532,8 @@ func (b *Bitfinex) GetHistoricTrades(p currency.Pair, assetType asset.Item, time
|
||||
if assetType == asset.MarginFunding {
|
||||
return nil, fmt.Errorf("asset type '%v' not supported", assetType)
|
||||
}
|
||||
if timestampStart.Equal(timestampEnd) || timestampEnd.After(time.Now()) || timestampEnd.Before(timestampStart) {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v", timestampStart, timestampEnd)
|
||||
if err := common.StartEndTimeCheck(timestampStart, timestampEnd); err != nil {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", timestampStart, timestampEnd, err)
|
||||
}
|
||||
var err error
|
||||
p, err = b.FormatExchangeCurrency(p, assetType)
|
||||
@@ -1025,7 +1025,10 @@ func (b *Bitfinex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
cf, err := b.fixCasing(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
@@ -1051,9 +1054,10 @@ func (b *Bitfinex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item,
|
||||
})
|
||||
}
|
||||
}
|
||||
err = dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", b.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
|
||||
@@ -461,8 +461,8 @@ func (b *Bitmex) GetHistoricTrades(p currency.Pair, assetType asset.Item, timest
|
||||
if assetType == asset.Index {
|
||||
return nil, fmt.Errorf("asset type '%v' not supported", assetType)
|
||||
}
|
||||
if timestampEnd.After(time.Now()) || timestampEnd.Before(timestampStart) {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v", timestampStart, timestampEnd)
|
||||
if err := common.StartEndTimeCheck(timestampStart, timestampEnd); err != nil {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", timestampStart, timestampEnd, err)
|
||||
}
|
||||
var err error
|
||||
p, err = b.FormatExchangeCurrency(p, assetType)
|
||||
|
||||
@@ -846,7 +846,10 @@ func (b *Bitstamp) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
formattedPair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
@@ -880,9 +883,10 @@ func (b *Bitstamp) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item,
|
||||
})
|
||||
}
|
||||
}
|
||||
err = dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", b.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
|
||||
@@ -56,6 +56,8 @@ var defaultSpotSubscribedChannelsAuth = []string{
|
||||
wsOrders,
|
||||
}
|
||||
|
||||
// TickerCache holds ticker and market summary data
|
||||
// in order to combine them when processing data
|
||||
type TickerCache struct {
|
||||
MarketSummaries map[string]*MarketSummaryData
|
||||
Tickers map[string]*TickerData
|
||||
|
||||
@@ -63,7 +63,7 @@ func (b *Bittrex) ProcessUpdateOB(pair currency.Pair, message *OrderbookUpdateMe
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLocalBuffer updates and returns the most recent iteration of the orderbook
|
||||
// UpdateLocalOBBuffer updates and returns the most recent iteration of the orderbook
|
||||
func (b *Bittrex) UpdateLocalOBBuffer(update *OrderbookUpdateMessage) (bool, error) {
|
||||
enabledPairs, err := b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
|
||||
@@ -976,7 +976,10 @@ func (b *BTCMarkets) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, s
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
for x := range dates.Ranges {
|
||||
var candles CandleResponse
|
||||
candles, err = b.GetMarketCandles(fPair.String(),
|
||||
@@ -1018,9 +1021,10 @@ func (b *BTCMarkets) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, s
|
||||
}
|
||||
}
|
||||
|
||||
err = dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", b.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
|
||||
@@ -910,7 +910,10 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(p currency.Pair, a asset.Item,
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, c.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, c.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
formattedPair, err := c.FormatExchangeCurrency(p, a)
|
||||
if err != nil {
|
||||
@@ -938,9 +941,10 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(p currency.Pair, a asset.Item,
|
||||
})
|
||||
}
|
||||
}
|
||||
err = dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", c.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", c.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
|
||||
@@ -1368,11 +1368,6 @@ func TestGetHistoricTrades(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// longer term
|
||||
_, err = f.GetHistoricTrades(enabledPairs.GetRandomPair(), assets[i], time.Now().Add(-time.Minute*60*310), time.Now().Add(-time.Minute*60*300))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ftx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -474,14 +475,10 @@ func (f *FTX) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.Da
|
||||
}
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
// FTX returns trades from the end date and iterates towards the start date
|
||||
func (f *FTX) GetHistoricTrades(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
|
||||
if timestampStart.Equal(timestampEnd) ||
|
||||
timestampEnd.After(time.Now()) ||
|
||||
timestampEnd.Before(timestampStart) ||
|
||||
(timestampStart.IsZero() && !timestampEnd.IsZero()) {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v",
|
||||
timestampStart,
|
||||
timestampEnd)
|
||||
if err := common.StartEndTimeCheck(timestampStart, timestampEnd); err != nil {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", timestampStart, timestampEnd, err)
|
||||
}
|
||||
var err error
|
||||
p, err = f.FormatExchangeCurrency(p, assetType)
|
||||
@@ -489,24 +486,32 @@ func (f *FTX) GetHistoricTrades(p currency.Pair, assetType asset.Item, timestamp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ts := timestampStart
|
||||
ts := timestampEnd
|
||||
var resp []trade.Data
|
||||
limit := 100
|
||||
allTrades:
|
||||
for {
|
||||
var trades []TradeData
|
||||
trades, err = f.GetTrades(p.String(),
|
||||
timestampStart.Unix(),
|
||||
ts.Unix(),
|
||||
timestampEnd.Unix(),
|
||||
100)
|
||||
if err != nil {
|
||||
if errors.Is(err, errStartTimeCannotBeAfterEndTime) {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(trades) == 0 {
|
||||
break
|
||||
}
|
||||
for i := 0; i < len(trades); i++ {
|
||||
if trades[i].Time.Before(timestampStart) || trades[i].Time.After(timestampEnd) {
|
||||
if timestampStart.Equal(trades[i].Time) || trades[i].Time.Before(timestampStart) {
|
||||
// reached end of trades to crawl
|
||||
break allTrades
|
||||
}
|
||||
if trades[i].Time.After(ts) {
|
||||
continue
|
||||
}
|
||||
var side order.Side
|
||||
side, err = order.StringToOrderSide(trades[i].Side)
|
||||
if err != nil {
|
||||
@@ -522,17 +527,11 @@ allTrades:
|
||||
Amount: trades[i].Size,
|
||||
Timestamp: trades[i].Time,
|
||||
})
|
||||
|
||||
if i == len(trades)-1 {
|
||||
if ts.Equal(trades[i].Time) {
|
||||
// reached end of trades to crawl
|
||||
break allTrades
|
||||
}
|
||||
ts = trades[i].Time
|
||||
}
|
||||
}
|
||||
if len(trades) != limit {
|
||||
break allTrades
|
||||
}
|
||||
}
|
||||
|
||||
err = f.AddTradesToBuffer(resp...)
|
||||
@@ -1073,7 +1072,10 @@ func (f *FTX) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, start, e
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, f.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, f.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
formattedPair, err := f.FormatExchangeCurrency(p, a)
|
||||
if err != nil {
|
||||
@@ -1101,9 +1103,10 @@ func (f *FTX) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, start, e
|
||||
})
|
||||
}
|
||||
}
|
||||
err = dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", f.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", f.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
|
||||
@@ -1189,8 +1189,8 @@ func TestGetHistoricTrades(t *testing.T) {
|
||||
tStart := time.Date(2020, 6, 6, 0, 0, 0, 0, time.UTC)
|
||||
tEnd := time.Date(2020, 6, 7, 0, 0, 0, 0, time.UTC)
|
||||
if !mockTests {
|
||||
tStart = time.Date(time.Now().Year(), time.Now().Month(), 6, 0, 0, 0, 0, time.UTC)
|
||||
tEnd = time.Date(time.Now().Year(), time.Now().Month(), 7, 0, 0, 0, 0, time.UTC)
|
||||
tStart = time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
tEnd = time.Date(time.Now().Year(), time.Now().Month(), 1, 1, 0, 0, 0, time.UTC)
|
||||
}
|
||||
_, err = g.GetHistoricTrades(currencyPair, asset.Spot, tStart, tEnd)
|
||||
if err != nil {
|
||||
|
||||
@@ -454,8 +454,8 @@ func (g *Gemini) GetRecentTrades(currencyPair currency.Pair, assetType asset.Ite
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
func (g *Gemini) GetHistoricTrades(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
|
||||
if timestampEnd.After(time.Now()) || timestampEnd.Before(timestampStart) {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v", timestampStart, timestampEnd)
|
||||
if err := common.StartEndTimeCheck(timestampStart, timestampEnd); err != nil && !errors.Is(err, common.ErrDateUnset) {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", timestampStart, timestampEnd, err)
|
||||
}
|
||||
var err error
|
||||
p, err = g.FormatExchangeCurrency(p, assetType)
|
||||
|
||||
@@ -470,8 +470,8 @@ func (h *HitBTC) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
func (h *HitBTC) GetHistoricTrades(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
|
||||
if timestampEnd.After(time.Now()) || timestampEnd.Before(timestampStart) {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v", timestampStart, timestampEnd)
|
||||
if err := common.StartEndTimeCheck(timestampStart, timestampEnd); err != nil {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", timestampStart, timestampEnd, err)
|
||||
}
|
||||
var err error
|
||||
p, err = h.FormatExchangeCurrency(p, assetType)
|
||||
@@ -851,7 +851,10 @@ func (h *HitBTC) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, st
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, h.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, h.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
formattedPair, err := h.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
@@ -878,9 +881,10 @@ func (h *HitBTC) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, st
|
||||
})
|
||||
}
|
||||
}
|
||||
err = dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", h.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", h.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
@@ -335,10 +334,17 @@ func (i *Interval) IntervalsPerYear() float64 {
|
||||
// CalculateCandleDateRanges will calculate the expected candle data in intervals in a date range
|
||||
// If an API is limited in the amount of candles it can make in a request, it will automatically separate
|
||||
// ranges into the limit
|
||||
func CalculateCandleDateRanges(start, end time.Time, interval Interval, limit uint32) IntervalRangeHolder {
|
||||
func CalculateCandleDateRanges(start, end time.Time, interval Interval, limit uint32) (*IntervalRangeHolder, error) {
|
||||
if err := common.StartEndTimeCheck(start, end); err != nil && !errors.Is(err, common.ErrStartAfterTimeNow) {
|
||||
return nil, err
|
||||
}
|
||||
if interval <= 0 {
|
||||
return nil, ErrUnsetInterval
|
||||
}
|
||||
|
||||
start = start.Round(interval.Duration())
|
||||
end = end.Round(interval.Duration())
|
||||
resp := IntervalRangeHolder{
|
||||
resp := &IntervalRangeHolder{
|
||||
Start: CreateIntervalTime(start),
|
||||
End: CreateIntervalTime(end),
|
||||
}
|
||||
@@ -355,7 +361,7 @@ func CalculateCandleDateRanges(start, end time.Time, interval Interval, limit ui
|
||||
End: CreateIntervalTime(end),
|
||||
Intervals: intervalsInWholePeriod,
|
||||
}}
|
||||
return resp
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var intervals []IntervalData
|
||||
@@ -376,7 +382,7 @@ func CalculateCandleDateRanges(start, end time.Time, interval Interval, limit ui
|
||||
})
|
||||
}
|
||||
|
||||
return resp
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// HasDataAtDate determines whether a there is any data at a set
|
||||
@@ -404,44 +410,74 @@ func (h *IntervalRangeHolder) HasDataAtDate(t time.Time) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// VerifyResultsHaveData will calculate whether there is data in each candle
|
||||
// SetHasDataFromCandles will calculate whether there is data in each candle
|
||||
// allowing any missing data from an API request to be highlighted
|
||||
func (h *IntervalRangeHolder) VerifyResultsHaveData(c []Candle) error {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(h.Ranges))
|
||||
func (h *IntervalRangeHolder) SetHasDataFromCandles(c []Candle) {
|
||||
for x := range h.Ranges {
|
||||
go func(iVal int) {
|
||||
for y := range h.Ranges[iVal].Intervals {
|
||||
for z := range c {
|
||||
cu := c[z].Time.Unix()
|
||||
if cu >= h.Ranges[iVal].Intervals[y].Start.Ticks && cu < h.Ranges[iVal].Intervals[y].End.Ticks {
|
||||
h.Ranges[iVal].Intervals[y].HasData = true
|
||||
break
|
||||
}
|
||||
intervals:
|
||||
for y := range h.Ranges[x].Intervals {
|
||||
for z := range c {
|
||||
cu := c[z].Time.Unix()
|
||||
if cu >= h.Ranges[x].Intervals[y].Start.Ticks && cu < h.Ranges[x].Intervals[y].End.Ticks {
|
||||
h.Ranges[x].Intervals[y].HasData = true
|
||||
continue intervals
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}(x)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var errs common.Errors
|
||||
for x := range h.Ranges {
|
||||
for y := range h.Ranges[x].Intervals {
|
||||
if !h.Ranges[x].Intervals[y].HasData {
|
||||
errs = append(errs, fmt.Errorf("between %v (%v) & %v (%v)",
|
||||
h.Ranges[x].Intervals[y].Start.Time,
|
||||
h.Ranges[x].Intervals[y].Start.Ticks,
|
||||
h.Ranges[x].Intervals[y].End.Time,
|
||||
h.Ranges[x].Intervals[y].End.Ticks))
|
||||
}
|
||||
h.Ranges[x].Intervals[y].HasData = false
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("%w - %v", ErrMissingCandleData, errs)
|
||||
}
|
||||
|
||||
// DataSummary returns a summary of a data range to highlight where data is missing
|
||||
func (h *IntervalRangeHolder) DataSummary(includeHasData bool) []string {
|
||||
var (
|
||||
rangeStart, rangeEnd, prevStart, prevEnd time.Time
|
||||
rangeHasData bool
|
||||
rangeTexts []string
|
||||
)
|
||||
rangeStart = h.Start.Time
|
||||
for i := range h.Ranges {
|
||||
for j := range h.Ranges[i].Intervals {
|
||||
if h.Ranges[i].Intervals[j].HasData {
|
||||
if !rangeHasData && !rangeEnd.IsZero() {
|
||||
rangeTexts = append(rangeTexts, h.createDateSummaryRange(rangeStart, rangeEnd, rangeHasData))
|
||||
prevStart = rangeStart
|
||||
prevEnd = rangeEnd
|
||||
rangeStart = h.Ranges[i].Intervals[j].Start.Time
|
||||
}
|
||||
rangeHasData = true
|
||||
} else {
|
||||
if rangeHasData && !rangeEnd.IsZero() {
|
||||
if includeHasData {
|
||||
rangeTexts = append(rangeTexts, h.createDateSummaryRange(rangeStart, rangeEnd, rangeHasData))
|
||||
}
|
||||
prevStart = rangeStart
|
||||
prevEnd = rangeEnd
|
||||
rangeStart = h.Ranges[i].Intervals[j].Start.Time
|
||||
}
|
||||
rangeHasData = false
|
||||
}
|
||||
rangeEnd = h.Ranges[i].Intervals[j].End.Time
|
||||
}
|
||||
}
|
||||
if !rangeStart.Equal(prevStart) || !rangeEnd.Equal(prevEnd) {
|
||||
if (rangeHasData && includeHasData) || !rangeHasData {
|
||||
rangeTexts = append(rangeTexts, h.createDateSummaryRange(rangeStart, rangeEnd, rangeHasData))
|
||||
}
|
||||
}
|
||||
return rangeTexts
|
||||
}
|
||||
|
||||
func (h *IntervalRangeHolder) createDateSummaryRange(start, end time.Time, hasData bool) string {
|
||||
dataString := "missing"
|
||||
if hasData {
|
||||
dataString = "has"
|
||||
}
|
||||
|
||||
return nil
|
||||
return fmt.Sprintf("%s data between %s and %s",
|
||||
dataString,
|
||||
start.Format(common.SimpleTimeFormat),
|
||||
end.Format(common.SimpleTimeFormat))
|
||||
}
|
||||
|
||||
// CreateIntervalTime is a simple helper function to set the time twice
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
@@ -398,27 +399,56 @@ func TestTotalCandlesPerInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCalculateCandleDateRanges(t *testing.T) {
|
||||
start := time.Unix(1546300800, 0)
|
||||
end := time.Unix(1577836799, 0)
|
||||
pt := time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
ft := time.Date(2222, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
et := time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC)
|
||||
nt := time.Time{}
|
||||
|
||||
v := CalculateCandleDateRanges(start, end, OneMin, 300)
|
||||
|
||||
if v.Ranges[0].Start.Ticks != time.Unix(1546300800, 0).Unix() {
|
||||
t.Errorf("expected %v received %v", 1546300800, v.Ranges[0].Start.Ticks)
|
||||
_, err := CalculateCandleDateRanges(nt, nt, OneMin, 300)
|
||||
if !errors.Is(err, common.ErrDateUnset) {
|
||||
t.Errorf("received %v expected %v", err, common.ErrDateUnset)
|
||||
}
|
||||
|
||||
v = CalculateCandleDateRanges(time.Now(), time.Now().AddDate(0, 0, 1), OneDay, 100)
|
||||
if len(v.Ranges) != 1 {
|
||||
t.Fatalf("expected %v received %v", 1, len(v.Ranges))
|
||||
_, err = CalculateCandleDateRanges(et, pt, OneMin, 300)
|
||||
if !errors.Is(err, common.ErrStartAfterEnd) {
|
||||
t.Errorf("received %v expected %v", err, common.ErrStartAfterEnd)
|
||||
}
|
||||
if len(v.Ranges[0].Intervals) != 1 {
|
||||
t.Errorf("expected %v received %v", 1, len(v.Ranges[0].Intervals))
|
||||
|
||||
_, err = CalculateCandleDateRanges(et, ft, 0, 300)
|
||||
if !errors.Is(err, ErrUnsetInterval) {
|
||||
t.Errorf("received %v expected %v", err, ErrUnsetInterval)
|
||||
}
|
||||
start = time.Now()
|
||||
end = time.Now().AddDate(0, 0, 10)
|
||||
v = CalculateCandleDateRanges(start, end, OneDay, 5)
|
||||
if len(v.Ranges) != 2 {
|
||||
t.Errorf("expected %v received %v", 2, len(v.Ranges))
|
||||
|
||||
_, err = CalculateCandleDateRanges(et, et, OneMin, 300)
|
||||
if !errors.Is(err, common.ErrStartEqualsEnd) {
|
||||
t.Errorf("received %v expected %v", err, common.ErrStartEqualsEnd)
|
||||
}
|
||||
|
||||
v, err := CalculateCandleDateRanges(pt, et, OneMin, 300)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if v.Ranges[0].Start.Ticks != time.Unix(915148800, 0).Unix() {
|
||||
t.Errorf("expected %v received %v", 915148800, v.Ranges[0].Start.Ticks)
|
||||
}
|
||||
|
||||
v, err = CalculateCandleDateRanges(pt, et, OneDay, 100)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(v.Ranges) != 77 {
|
||||
t.Fatalf("expected %v received %v", 77, len(v.Ranges))
|
||||
}
|
||||
if len(v.Ranges[0].Intervals) != 100 {
|
||||
t.Errorf("expected %v received %v", 100, len(v.Ranges[0].Intervals))
|
||||
}
|
||||
v, err = CalculateCandleDateRanges(et, ft, OneDay, 5)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(v.Ranges) != 14756 {
|
||||
t.Errorf("expected %v received %v", 14756, len(v.Ranges))
|
||||
}
|
||||
if len(v.Ranges[0].Intervals) != 5 {
|
||||
t.Errorf("expected %v received %v", 5, len(v.Ranges[0].Intervals))
|
||||
@@ -426,8 +456,10 @@ func TestCalculateCandleDateRanges(t *testing.T) {
|
||||
if len(v.Ranges[1].Intervals) != 5 {
|
||||
t.Errorf("expected %v received %v", 5, len(v.Ranges[1].Intervals))
|
||||
}
|
||||
if !v.Ranges[1].Intervals[4].End.Equal(end.Round(OneDay.Duration())) {
|
||||
t.Errorf("expected %v received %v", end.Round(OneDay.Duration()), v.Ranges[1].Intervals[4].End)
|
||||
lenRanges := len(v.Ranges) - 1
|
||||
lenIntervals := len(v.Ranges[lenRanges].Intervals) - 1
|
||||
if !v.Ranges[lenRanges].Intervals[lenIntervals].End.Equal(ft.Round(OneDay.Duration())) {
|
||||
t.Errorf("expected %v received %v", ft.Round(OneDay.Duration()), v.Ranges[lenRanges].Intervals[lenIntervals].End)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,38 +744,70 @@ func TestLoadCSV(t *testing.T) {
|
||||
func TestVerifyResultsHaveData(t *testing.T) {
|
||||
tt2 := time.Now().Round(OneDay.Duration())
|
||||
tt1 := time.Now().Add(-time.Hour * 24).Round(OneDay.Duration())
|
||||
dateRanges := CalculateCandleDateRanges(tt1, tt2, OneDay, 0)
|
||||
dateRanges, err := CalculateCandleDateRanges(tt1, tt2, OneDay, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if dateRanges.HasDataAtDate(tt1) {
|
||||
t.Error("unexpected true value")
|
||||
}
|
||||
|
||||
err := dateRanges.VerifyResultsHaveData(nil)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), ErrMissingCandleData.Error()) {
|
||||
t.Errorf("expected %v", ErrMissingCandleData)
|
||||
}
|
||||
|
||||
err = dateRanges.VerifyResultsHaveData([]Candle{
|
||||
dateRanges.SetHasDataFromCandles([]Candle{
|
||||
{
|
||||
Time: tt1,
|
||||
},
|
||||
})
|
||||
if !dateRanges.HasDataAtDate(tt1) {
|
||||
t.Error("expected true")
|
||||
}
|
||||
dateRanges.SetHasDataFromCandles([]Candle{
|
||||
{
|
||||
Time: tt2,
|
||||
},
|
||||
})
|
||||
if dateRanges.HasDataAtDate(tt1) {
|
||||
t.Error("expected false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataSummary(t *testing.T) {
|
||||
tt1 := time.Now().Add(-time.Hour * 24).Round(OneDay.Duration())
|
||||
tt2 := time.Now().Round(OneDay.Duration())
|
||||
tt3 := time.Now().Add(time.Hour * 24).Round(OneDay.Duration())
|
||||
dateRanges, err := CalculateCandleDateRanges(tt1, tt2, OneDay, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
result := dateRanges.DataSummary(false)
|
||||
if len(result) != 1 {
|
||||
t.Errorf("expected %v received %v", 1, len(result))
|
||||
}
|
||||
dateRanges, err = CalculateCandleDateRanges(tt1, tt3, OneDay, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
dateRanges.Ranges[0].Intervals[0].HasData = true
|
||||
result = dateRanges.DataSummary(true)
|
||||
if len(result) != 2 {
|
||||
t.Errorf("expected %v received %v", 2, len(result))
|
||||
}
|
||||
result = dateRanges.DataSummary(false)
|
||||
if len(result) != 1 {
|
||||
t.Errorf("expected %v received %v", 1, len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasDataAtDate(t *testing.T) {
|
||||
tt2 := time.Now().Round(OneDay.Duration())
|
||||
tt1 := time.Now().Add(-time.Hour * 24 * 30).Round(OneDay.Duration())
|
||||
dateRanges := CalculateCandleDateRanges(tt1, tt2, OneDay, 0)
|
||||
dateRanges, err := CalculateCandleDateRanges(tt1, tt2, OneDay, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if dateRanges.HasDataAtDate(tt1) {
|
||||
t.Error("unexpected true value")
|
||||
}
|
||||
|
||||
_ = dateRanges.VerifyResultsHaveData([]Candle{
|
||||
dateRanges.SetHasDataFromCandles([]Candle{
|
||||
{
|
||||
Time: tt1,
|
||||
},
|
||||
|
||||
@@ -41,6 +41,11 @@ const (
|
||||
var (
|
||||
// ErrMissingCandleData is an error for missing candle data
|
||||
ErrMissingCandleData = errors.New("missing candle data")
|
||||
// ErrUnsetInterval is an error for date range calculation
|
||||
ErrUnsetInterval = errors.New("cannot calculate range, interval unset")
|
||||
// ErrUnsupportedInterval returns when the provided interval is not supported by an exchange
|
||||
ErrUnsupportedInterval = errors.New("interval unsupported by exchange")
|
||||
|
||||
// SupportedIntervals is a list of all supported intervals
|
||||
SupportedIntervals = []Interval{
|
||||
FifteenSecond,
|
||||
|
||||
@@ -361,8 +361,8 @@ func (l *Lbank) GetRecentTrades(p currency.Pair, assetType asset.Item) ([]trade.
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
func (l *Lbank) GetHistoricTrades(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
|
||||
if timestampEnd.After(time.Now()) || timestampEnd.Before(timestampStart) {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v", timestampStart, timestampEnd)
|
||||
if err := common.StartEndTimeCheck(timestampStart, timestampEnd); err != nil {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", timestampStart, timestampEnd, err)
|
||||
}
|
||||
var err error
|
||||
p, err = l.FormatExchangeCurrency(p, assetType)
|
||||
@@ -937,7 +937,10 @@ func (l *Lbank) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, sta
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, l.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, l.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
formattedPair, err := l.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
@@ -967,9 +970,10 @@ func (l *Lbank) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, sta
|
||||
}
|
||||
}
|
||||
|
||||
err = dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", l.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", l.Name, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
|
||||
@@ -669,8 +669,8 @@ func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startTime := time.Unix(1607494054, 0)
|
||||
endTime := time.Unix(1607512054, 0)
|
||||
_, err = o.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, endTime, kline.OneWeek)
|
||||
endTime := time.Unix(1607594054, 0)
|
||||
_, err = o.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, endTime, kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -671,7 +671,10 @@ func (o *OKGroup) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, s
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
dates := kline.CalculateCandleDateRanges(start, end, interval, o.Features.Enabled.Kline.ResultLimit)
|
||||
dates, err := kline.CalculateCandleDateRanges(start, end, interval, o.Features.Enabled.Kline.ResultLimit)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
formattedPair, err := o.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
@@ -730,9 +733,10 @@ func (o *OKGroup) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, s
|
||||
}
|
||||
}
|
||||
|
||||
err = dates.VerifyResultsHaveData(ret.Candles)
|
||||
if err != nil {
|
||||
log.Warnf(log.ExchangeSys, "%s - %s", o.Name, err)
|
||||
dates.SetHasDataFromCandles(ret.Candles)
|
||||
summary := dates.DataSummary(false)
|
||||
if len(summary) > 0 {
|
||||
log.Warnf(log.ExchangeSys, "%v - %v", o.ExchangeName, summary)
|
||||
}
|
||||
ret.RemoveDuplicates()
|
||||
ret.RemoveOutsideRange(start, end)
|
||||
|
||||
@@ -440,8 +440,8 @@ func (p *Poloniex) GetRecentTrades(currencyPair currency.Pair, assetType asset.I
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
func (p *Poloniex) GetHistoricTrades(currencyPair currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
|
||||
if timestampEnd.After(time.Now()) || timestampEnd.Before(timestampStart) {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v", timestampStart, timestampEnd)
|
||||
if err := common.StartEndTimeCheck(timestampStart, timestampEnd); err != nil {
|
||||
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", timestampStart, timestampEnd, err)
|
||||
}
|
||||
var err error
|
||||
currencyPair, err = p.FormatExchangeCurrency(currencyPair, assetType)
|
||||
|
||||
@@ -506,7 +506,6 @@ func (w *Websocket) trafficMonitor() {
|
||||
w.trafficTimeout)
|
||||
}
|
||||
trafficTimer.Stop()
|
||||
w.Wg.Done()
|
||||
if !w.IsConnecting() && w.IsConnected() {
|
||||
err := w.Shutdown()
|
||||
if err != nil {
|
||||
@@ -516,6 +515,7 @@ func (w *Websocket) trafficMonitor() {
|
||||
}
|
||||
}
|
||||
w.setTrafficMonitorRunning(false)
|
||||
w.Wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -158,26 +158,28 @@ func TestSetup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTrafficMonitorTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
ws := *New()
|
||||
err := ws.Setup(defaultSetup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ws.trafficTimeout = time.Second
|
||||
ws.trafficTimeout = time.Millisecond
|
||||
ws.ShutdownC = make(chan struct{})
|
||||
ws.trafficMonitor()
|
||||
if !ws.IsTrafficMonitorRunning() {
|
||||
t.Fatal("traffic monitor should be running")
|
||||
}
|
||||
// Deploy traffic alert
|
||||
ws.TrafficAlert <- struct{}{}
|
||||
// try to add another traffic monitor
|
||||
ws.trafficMonitor()
|
||||
if !ws.IsTrafficMonitorRunning() {
|
||||
t.Fatal("traffic monitor should be running")
|
||||
}
|
||||
|
||||
// Deploy traffic alert
|
||||
ws.TrafficAlert <- struct{}{}
|
||||
time.Sleep(time.Second * 2)
|
||||
// prevent shutdown routine
|
||||
ws.setConnectedStatus(false)
|
||||
// await timeout closure
|
||||
ws.Wg.Wait()
|
||||
if ws.IsTrafficMonitorRunning() {
|
||||
t.Error("should be ded")
|
||||
@@ -547,7 +549,7 @@ func TestConnectionMonitorNoConnection(t *testing.T) {
|
||||
if !ws.IsConnectionMonitorRunning() {
|
||||
t.Fatal("Should not have exited")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
if ws.IsConnectionMonitorRunning() {
|
||||
t.Fatal("Should have exited")
|
||||
}
|
||||
@@ -557,7 +559,7 @@ func TestConnectionMonitorNoConnection(t *testing.T) {
|
||||
if !ws.IsConnectionMonitorRunning() {
|
||||
t.Fatal("Should not have exited")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
if ws.IsConnectionMonitorRunning() {
|
||||
t.Fatal("Should have exited")
|
||||
}
|
||||
@@ -696,7 +698,7 @@ func TestSendMessage(t *testing.T) {
|
||||
func TestSendMessageWithResponse(t *testing.T) {
|
||||
wc := &WebsocketConnection{
|
||||
Verbose: true,
|
||||
URL: "wss://echo.websocket.org",
|
||||
URL: "wss://ws.kraken.com",
|
||||
ResponseMaxLimit: time.Second * 5,
|
||||
Match: NewMatch(),
|
||||
}
|
||||
@@ -756,7 +758,7 @@ func readMessages(wc *WebsocketConnection, t *testing.T) {
|
||||
// TestSetupPingHandler logic test
|
||||
func TestSetupPingHandler(t *testing.T) {
|
||||
wc := &WebsocketConnection{
|
||||
URL: "wss://echo.websocket.org",
|
||||
URL: websocketTestURL,
|
||||
ResponseMaxLimit: time.Second * 5,
|
||||
Match: NewMatch(),
|
||||
Wg: &sync.WaitGroup{},
|
||||
@@ -774,7 +776,7 @@ func TestSetupPingHandler(t *testing.T) {
|
||||
wc.SetupPingHandler(PingHandler{
|
||||
UseGorillaHandler: true,
|
||||
MessageType: websocket.PingMessage,
|
||||
Delay: 1000,
|
||||
Delay: 100,
|
||||
})
|
||||
|
||||
err = wc.Connection.Close()
|
||||
@@ -791,7 +793,7 @@ func TestSetupPingHandler(t *testing.T) {
|
||||
Message: []byte(Ping),
|
||||
Delay: 200,
|
||||
})
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
time.Sleep(time.Millisecond * 201)
|
||||
close(wc.ShutdownC)
|
||||
wc.Wg.Wait()
|
||||
}
|
||||
@@ -799,7 +801,7 @@ func TestSetupPingHandler(t *testing.T) {
|
||||
// TestParseBinaryResponse logic test
|
||||
func TestParseBinaryResponse(t *testing.T) {
|
||||
wc := &WebsocketConnection{
|
||||
URL: "wss://echo.websocket.org",
|
||||
URL: websocketTestURL,
|
||||
ResponseMaxLimit: time.Second * 5,
|
||||
Match: NewMatch(),
|
||||
}
|
||||
@@ -1241,7 +1243,7 @@ func TestWebsocketConnectionShutdown(t *testing.T) {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
|
||||
wc.URL = "wss://echo.websocket.org"
|
||||
wc.URL = websocketTestURL
|
||||
|
||||
err = wc.Dial(&websocket.Dialer{}, nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -136,6 +136,14 @@ func GetTradesInRange(exchangeName, assetType, base, quote string, startDate, en
|
||||
return SQLDataToTrade(results...)
|
||||
}
|
||||
|
||||
// HasTradesInRanges Creates an executes an SQL query to verify if a trade exists within a timeframe
|
||||
func HasTradesInRanges(exchangeName, assetType, base, quote string, rangeHolder *kline.IntervalRangeHolder) error {
|
||||
if exchangeName == "" || assetType == "" || base == "" || quote == "" {
|
||||
return errors.New("invalid arguments received")
|
||||
}
|
||||
return tradesql.VerifyTradeInIntervals(exchangeName, assetType, base, quote, rangeHolder)
|
||||
}
|
||||
|
||||
func tradeToSQLData(trades ...Data) ([]tradesql.Data, error) {
|
||||
sort.Sort(ByDate(trades))
|
||||
var results []tradesql.Data
|
||||
@@ -195,7 +203,7 @@ func SQLDataToTrade(dbTrades ...tradesql.Data) (result []Data, err error) {
|
||||
// ConvertTradesToCandles turns trade data into kline.Items
|
||||
func ConvertTradesToCandles(interval kline.Interval, trades ...Data) (kline.Item, error) {
|
||||
if len(trades) == 0 {
|
||||
return kline.Item{}, errors.New("no trades supplied")
|
||||
return kline.Item{}, ErrNoTradesSupplied
|
||||
}
|
||||
groupedData := groupTradesToInterval(interval, trades...)
|
||||
candles := kline.Item{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package trade
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -19,6 +20,8 @@ var (
|
||||
// BufferProcessorIntervalTime is the interval to save trade buffer data to the database.
|
||||
// Change this by changing the runtime param `-tradeprocessinginterval=15s`
|
||||
BufferProcessorIntervalTime = DefaultProcessorIntervalTime
|
||||
// ErrNoTradesSupplied is returned when an attempt is made to process trades, but is an empty slice
|
||||
ErrNoTradesSupplied = errors.New("no trades supplied")
|
||||
)
|
||||
|
||||
// Data defines trade data
|
||||
|
||||
@@ -2,6 +2,7 @@ package zb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -928,11 +929,11 @@ func Test_FormatExchangeKlineInterval(t *testing.T) {
|
||||
|
||||
func TestValidateCandlesRequest(t *testing.T) {
|
||||
_, err := z.validateCandlesRequest(currency.Pair{}, "", time.Time{}, time.Time{}, kline.Interval(-1))
|
||||
if err != nil && err.Error() != "invalid time range supplied. Start: 0001-01-01 00:00:00 +0000 UTC End 0001-01-01 00:00:00 +0000 UTC" {
|
||||
if !errors.Is(err, common.ErrDateUnset) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = z.validateCandlesRequest(currency.Pair{}, "", time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC), time.Time{}, kline.Interval(-1))
|
||||
if err != nil && err.Error() != "invalid time range supplied. Start: 2020-01-01 01:01:01.000000001 +0000 UTC End 0001-01-01 00:00:00 +0000 UTC" {
|
||||
if !errors.Is(err, common.ErrDateUnset) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = z.validateCandlesRequest(currency.Pair{}, asset.Spot, time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC), time.Date(2020, 1, 1, 1, 1, 1, 3, time.UTC), kline.OneHour)
|
||||
|
||||
@@ -934,13 +934,8 @@ allKlines:
|
||||
}
|
||||
|
||||
func (z *ZB) validateCandlesRequest(p currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
if start.Equal(end) ||
|
||||
end.After(time.Now()) ||
|
||||
end.Before(start) ||
|
||||
(start.IsZero() && !end.IsZero()) {
|
||||
return kline.Item{}, fmt.Errorf("invalid time range supplied. Start: %v End %v",
|
||||
start,
|
||||
end)
|
||||
if err := common.StartEndTimeCheck(start, end); err != nil {
|
||||
return kline.Item{}, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", start, end, err)
|
||||
}
|
||||
if err := z.ValidateKline(p, a, interval); err != nil {
|
||||
return kline.Item{}, err
|
||||
|
||||
Reference in New Issue
Block a user