mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-19 23:16:48 +00:00
* Kline: Fix Raw Short, Marshal and Unmarshal * Deribit: Rename GenerateDefaultSubs * Deribit: Remove custom GetDefaultConfig Moved to exchange base by #1472 * Deribit: Straight Rename of eps to endpoints Since I had to ask what this abbreviation meant, I think we should abandon it * Deribit: Add Subscription configuration * Deribit: Fix race on Setup with optionsRegex Calling Setup twice would race on the assignment to this package var. There was an option to just move the assignment to the package var declaration, but this change improves the performance and allocations: ``` BenchmarkOptionPairToString-8 1000000 1239 ns/op 485 B/op 10 allocs/op BenchmarkOptionPairToString2-8 3473804 656.2 ns/op 348 B/op 7 allocs/op ``` I've also removed the t.Run because even success the -v output from tests would be very noisy, and I don't think we were getting any benefit from it at all: ``` === RUN TestOptionPairToString === PAUSE TestOptionPairToString === CONT TestOptionPairToString === RUN TestOptionPairToString/BTC-30MAY24-61000-C === PAUSE TestOptionPairToString/BTC-30MAY24-61000-C === RUN TestOptionPairToString/ETH-1JUN24-3200-P === PAUSE TestOptionPairToString/ETH-1JUN24-3200-P === RUN TestOptionPairToString/SOL_USDC-31MAY24-162-P === PAUSE TestOptionPairToString/SOL_USDC-31MAY24-162-P === RUN TestOptionPairToString/MATIC_USDC-6APR24-0d98-P === PAUSE TestOptionPairToString/MATIC_USDC-6APR24-0d98-P === CONT TestOptionPairToString/BTC-30MAY24-61000-C === CONT TestOptionPairToString/SOL_USDC-31MAY24-162-P === CONT TestOptionPairToString/ETH-1JUN24-3200-P === CONT TestOptionPairToString/MATIC_USDC-6APR24-0d98-P --- PASS: TestOptionPairToString (0.00s) --- PASS: TestOptionPairToString/BTC-30MAY24-61000-C (0.00s) --- PASS: TestOptionPairToString/ETH-1JUN24-3200-P (0.00s) --- PASS: TestOptionPairToString/SOL_USDC-31MAY24-162-P (0.00s) --- PASS: TestOptionPairToString/MATIC_USDC-6APR24-0d98-P (0.00s) ``` ( And that got worse with me adding more tests )
1418 lines
31 KiB
Go
1418 lines
31 KiB
Go
package kline
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/database"
|
|
"github.com/thrasher-corp/gocryptotrader/database/drivers"
|
|
"github.com/thrasher-corp/gocryptotrader/database/repository/candle"
|
|
"github.com/thrasher-corp/gocryptotrader/database/repository/exchange"
|
|
"github.com/thrasher-corp/gocryptotrader/database/testhelpers"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
|
)
|
|
|
|
var (
|
|
verbose = false
|
|
testExchanges = []exchange.Details{{Name: "one"}}
|
|
)
|
|
|
|
func TestValidateData(t *testing.T) {
|
|
t.Parallel()
|
|
err := validateData(nil)
|
|
if err == nil {
|
|
t.Error("error cannot be nil")
|
|
}
|
|
|
|
var empty []order.TradeHistory
|
|
err = validateData(empty)
|
|
if err == nil {
|
|
t.Error("error cannot be nil")
|
|
}
|
|
|
|
tn := time.Now()
|
|
trade1 := []order.TradeHistory{
|
|
{Timestamp: tn.Add(2 * time.Minute), TID: "2"},
|
|
{Timestamp: tn.Add(time.Minute), TID: "1"},
|
|
{Timestamp: tn.Add(3 * time.Minute), TID: "3"},
|
|
}
|
|
|
|
err = validateData(trade1)
|
|
if err == nil {
|
|
t.Error("error cannot be nil")
|
|
}
|
|
|
|
trade2 := []order.TradeHistory{
|
|
{Timestamp: tn.Add(2 * time.Minute), TID: "2", Amount: 1, Price: 0},
|
|
}
|
|
|
|
err = validateData(trade2)
|
|
if err == nil {
|
|
t.Error("error cannot be nil")
|
|
}
|
|
|
|
trade3 := []order.TradeHistory{
|
|
{TID: "2", Amount: 1, Price: 0},
|
|
}
|
|
|
|
err = validateData(trade3)
|
|
if err == nil {
|
|
t.Error("error cannot be nil")
|
|
}
|
|
|
|
trade4 := []order.TradeHistory{
|
|
{Timestamp: tn.Add(2 * time.Minute), TID: "2", Amount: 1, Price: 1000},
|
|
{Timestamp: tn.Add(time.Minute), TID: "1", Amount: 1, Price: 1001},
|
|
{Timestamp: tn.Add(3 * time.Minute), TID: "3", Amount: 1, Price: 1001.5},
|
|
}
|
|
|
|
err = validateData(trade4)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if trade4[0].TID != "1" || trade4[1].TID != "2" || trade4[2].TID != "3" {
|
|
t.Error("trade history sorted incorrectly")
|
|
}
|
|
}
|
|
|
|
func TestCreateKline(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pair := currency.NewPair(currency.BTC, currency.USD)
|
|
_, err := CreateKline(nil, OneMin, pair, asset.Spot, "Binance")
|
|
if !errors.Is(err, errInsufficientTradeData) {
|
|
t.Fatalf("received: '%v' but expected '%v'", err, errInsufficientTradeData)
|
|
}
|
|
|
|
tradeTotal := 24000
|
|
trades := make([]order.TradeHistory, tradeTotal)
|
|
execution := time.Now()
|
|
for x := range tradeTotal {
|
|
price, rndTime := 1000+float64(rand.Intn(1000)), rand.Intn(10) //nolint:gosec // no need to import crypo/rand for testing
|
|
execution = execution.Add(time.Duration(rndTime) * time.Second)
|
|
trades[x] = order.TradeHistory{
|
|
Timestamp: execution,
|
|
Amount: 1, // Keep as one for counting
|
|
Price: price,
|
|
}
|
|
}
|
|
|
|
_, err = CreateKline(trades, 0, pair, asset.Spot, "Binance")
|
|
if !errors.Is(err, ErrInvalidInterval) {
|
|
t.Fatalf("received: '%v' but expected '%v'", err, ErrInvalidInterval)
|
|
}
|
|
|
|
c, err := CreateKline(trades, OneMin, pair, asset.Spot, "Binance")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var amounts float64
|
|
for x := range c.Candles {
|
|
amounts += c.Candles[x].Volume
|
|
}
|
|
if amounts != float64(tradeTotal) {
|
|
t.Fatalf("received: '%v' but expected '%v'", amounts, float64(tradeTotal))
|
|
}
|
|
}
|
|
|
|
func TestKlineWord(t *testing.T) {
|
|
t.Parallel()
|
|
if OneDay.Word() != "oneday" {
|
|
t.Fatalf("unexpected result: %v", OneDay.Word())
|
|
}
|
|
}
|
|
|
|
func TestKlineDuration(t *testing.T) {
|
|
t.Parallel()
|
|
if OneDay.Duration() != time.Hour*24 {
|
|
t.Fatalf("unexpected result: %v", OneDay.Duration())
|
|
}
|
|
}
|
|
|
|
func TestKlineShort(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, "24h", OneDay.Short(), "One day should show as 24h")
|
|
assert.Equal(t, "1h", OneHour.Short(), "One hour should truncate 0m0s suffix")
|
|
assert.Equal(t, "raw", Raw.Short(), "Raw should return raw")
|
|
}
|
|
|
|
func TestDurationToWord(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
name string
|
|
interval Interval
|
|
}{
|
|
{
|
|
"raw",
|
|
Raw,
|
|
},
|
|
{
|
|
"hundredmillisec",
|
|
HundredMilliseconds,
|
|
},
|
|
{
|
|
"thousandmillisec",
|
|
ThousandMilliseconds,
|
|
},
|
|
{
|
|
"tensec",
|
|
TenSecond,
|
|
},
|
|
{
|
|
"FifteenSecond",
|
|
FifteenSecond,
|
|
},
|
|
{
|
|
"OneMin",
|
|
OneMin,
|
|
},
|
|
{
|
|
"ThreeMin",
|
|
ThreeMin,
|
|
},
|
|
{
|
|
"FiveMin",
|
|
FiveMin,
|
|
},
|
|
{
|
|
"TenMin",
|
|
TenMin,
|
|
},
|
|
{
|
|
"FifteenMin",
|
|
FifteenMin,
|
|
},
|
|
{
|
|
"ThirtyMin",
|
|
ThirtyMin,
|
|
},
|
|
{
|
|
"OneHour",
|
|
OneHour,
|
|
},
|
|
{
|
|
"TwoHour",
|
|
TwoHour,
|
|
},
|
|
{
|
|
"FourHour",
|
|
FourHour,
|
|
},
|
|
{
|
|
"SixHour",
|
|
SixHour,
|
|
},
|
|
{
|
|
"EightHour",
|
|
OneHour * 8,
|
|
},
|
|
{
|
|
"TwelveHour",
|
|
TwelveHour,
|
|
},
|
|
{
|
|
"OneDay",
|
|
OneDay,
|
|
},
|
|
{
|
|
"ThreeDay",
|
|
ThreeDay,
|
|
},
|
|
{
|
|
"FiveDay",
|
|
FiveDay,
|
|
},
|
|
{
|
|
"FifteenDay",
|
|
FifteenDay,
|
|
},
|
|
{
|
|
"OneWeek",
|
|
OneWeek,
|
|
},
|
|
{
|
|
"TwoWeek",
|
|
TwoWeek,
|
|
},
|
|
{
|
|
"OneMonth",
|
|
OneMonth,
|
|
},
|
|
{
|
|
"ThreeMonth",
|
|
ThreeMonth,
|
|
},
|
|
{
|
|
"SixMonth",
|
|
SixMonth,
|
|
},
|
|
{
|
|
"OneYear",
|
|
OneYear,
|
|
},
|
|
{
|
|
"notfound",
|
|
Interval(time.Hour * 1337),
|
|
},
|
|
}
|
|
for x := range testCases {
|
|
test := testCases[x]
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Helper()
|
|
v := durationToWord(test.interval)
|
|
if !strings.EqualFold(v, test.name) {
|
|
t.Fatalf("%v: received %v expected %v", test.name, v, test.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTotalCandlesPerInterval(t *testing.T) {
|
|
t.Parallel()
|
|
start := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
end := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
interval Interval
|
|
expected int64
|
|
}{
|
|
{
|
|
"FifteenSecond",
|
|
FifteenSecond,
|
|
2102400,
|
|
},
|
|
{
|
|
"OneMin",
|
|
OneMin,
|
|
525600,
|
|
},
|
|
{
|
|
"ThreeMin",
|
|
ThreeMin,
|
|
175200,
|
|
},
|
|
{
|
|
"FiveMin",
|
|
FiveMin,
|
|
105120,
|
|
},
|
|
{
|
|
"TenMin",
|
|
TenMin,
|
|
52560,
|
|
},
|
|
{
|
|
"FifteenMin",
|
|
FifteenMin,
|
|
35040,
|
|
},
|
|
{
|
|
"ThirtyMin",
|
|
ThirtyMin,
|
|
17520,
|
|
},
|
|
{
|
|
"OneHour",
|
|
OneHour,
|
|
8760,
|
|
},
|
|
{
|
|
"TwoHour",
|
|
TwoHour,
|
|
4380,
|
|
},
|
|
{
|
|
"FourHour",
|
|
FourHour,
|
|
2190,
|
|
},
|
|
{
|
|
"SixHour",
|
|
SixHour,
|
|
1460,
|
|
},
|
|
{
|
|
"EightHour",
|
|
OneHour * 8,
|
|
1095,
|
|
},
|
|
{
|
|
"TwelveHour",
|
|
TwelveHour,
|
|
730,
|
|
},
|
|
{
|
|
"OneDay",
|
|
OneDay,
|
|
365,
|
|
},
|
|
{
|
|
"ThreeDay",
|
|
ThreeDay,
|
|
121,
|
|
},
|
|
{
|
|
"FiveDay",
|
|
FiveDay,
|
|
73,
|
|
},
|
|
{
|
|
"FifteenDay",
|
|
FifteenDay,
|
|
24,
|
|
},
|
|
{
|
|
"OneWeek",
|
|
OneWeek,
|
|
52,
|
|
},
|
|
{
|
|
"TwoWeek",
|
|
TwoWeek,
|
|
26,
|
|
},
|
|
{
|
|
"OneMonth",
|
|
OneMonth,
|
|
12,
|
|
},
|
|
{
|
|
"OneYear",
|
|
OneYear,
|
|
1,
|
|
},
|
|
}
|
|
for x := range testCases {
|
|
test := testCases[x]
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
v := TotalCandlesPerInterval(start, end, test.interval)
|
|
if v != test.expected {
|
|
t.Fatalf("%v: received %v expected %v", test.name, v, test.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCalculateCandleDateRanges(t *testing.T) {
|
|
t.Parallel()
|
|
pt := time.Date(2019, 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{}
|
|
|
|
_, err := CalculateCandleDateRanges(nt, nt, OneMin, 300)
|
|
if !errors.Is(err, common.ErrDateUnset) {
|
|
t.Errorf("received %v expected %v", err, common.ErrDateUnset)
|
|
}
|
|
|
|
_, err = CalculateCandleDateRanges(et, pt, OneMin, 300)
|
|
if !errors.Is(err, common.ErrStartAfterEnd) {
|
|
t.Errorf("received %v expected %v", err, common.ErrStartAfterEnd)
|
|
}
|
|
|
|
_, err = CalculateCandleDateRanges(et, ft, 0, 300)
|
|
if !errors.Is(err, ErrInvalidInterval) {
|
|
t.Errorf("received %v expected %v", err, ErrInvalidInterval)
|
|
}
|
|
|
|
_, 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, OneWeek, 300)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if !v.Ranges[0].Start.Time.Equal(time.Unix(1546214400, 0)) {
|
|
t.Errorf("expected %v received %v", 1546214400, v.Ranges[0].Start.Ticks)
|
|
}
|
|
|
|
v, err = CalculateCandleDateRanges(pt, et, OneWeek, 100)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if len(v.Ranges) != 1 {
|
|
t.Fatalf("expected %v received %v", 1, len(v.Ranges))
|
|
}
|
|
if len(v.Ranges[0].Intervals) != 52 {
|
|
t.Errorf("expected %v received %v", 52, len(v.Ranges[0].Intervals))
|
|
}
|
|
v, err = CalculateCandleDateRanges(et, ft, OneWeek, 5)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if len(v.Ranges) != 2108 {
|
|
t.Errorf("expected %v received %v", 2108, len(v.Ranges))
|
|
}
|
|
if len(v.Ranges[0].Intervals) != 5 {
|
|
t.Errorf("expected %v received %v", 5, len(v.Ranges[0].Intervals))
|
|
}
|
|
if len(v.Ranges[1].Intervals) != 5 {
|
|
t.Errorf("expected %v received %v", 5, len(v.Ranges[1].Intervals))
|
|
}
|
|
lenRanges := len(v.Ranges) - 1
|
|
lenIntervals := len(v.Ranges[lenRanges].Intervals) - 1
|
|
if !v.Ranges[lenRanges].Intervals[lenIntervals].End.Equal(ft.Round(OneWeek.Duration())) {
|
|
t.Errorf("expected %v received %v", ft.Round(OneDay.Duration()), v.Ranges[lenRanges].Intervals[lenIntervals].End)
|
|
}
|
|
}
|
|
|
|
func TestItem_SortCandlesByTimestamp(t *testing.T) {
|
|
t.Parallel()
|
|
var tempKline = Item{
|
|
Exchange: "testExchange",
|
|
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
|
Asset: asset.Spot,
|
|
Interval: OneDay,
|
|
}
|
|
|
|
for x := range 100 {
|
|
y := rand.Float64() //nolint:gosec // used for generating test data, no need to import crypo/rand
|
|
tempKline.Candles = append(tempKline.Candles,
|
|
Candle{
|
|
Time: time.Now().AddDate(0, 0, -x),
|
|
Open: y,
|
|
High: y + float64(x),
|
|
Low: y - float64(x),
|
|
Close: y,
|
|
Volume: y,
|
|
})
|
|
}
|
|
|
|
tempKline.SortCandlesByTimestamp(false)
|
|
if tempKline.Candles[0].Time.After(tempKline.Candles[1].Time) {
|
|
t.Fatal("expected kline.Candles to be in descending order")
|
|
}
|
|
|
|
tempKline.SortCandlesByTimestamp(true)
|
|
if tempKline.Candles[0].Time.Before(tempKline.Candles[1].Time) {
|
|
t.Fatal("expected kline.Candles to be in ascending order")
|
|
}
|
|
}
|
|
|
|
func setupTest(t *testing.T) {
|
|
t.Helper()
|
|
if verbose {
|
|
err := testhelpers.EnableVerboseTestOutput()
|
|
if err != nil {
|
|
fmt.Printf("failed to enable verbose test output: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
var err error
|
|
testhelpers.MigrationDir = filepath.Join("..", "..", "database", "migrations")
|
|
testhelpers.PostgresTestDatabase = testhelpers.GetConnectionDetails()
|
|
testhelpers.TempDir, err = os.MkdirTemp("", "gct-temp")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp file: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestStoreInDatabase(t *testing.T) {
|
|
setupTest(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
config *database.Config
|
|
seedDB func(bool) error
|
|
runner func(t *testing.T)
|
|
closer func(dbConn *database.Instance) error
|
|
}{
|
|
{
|
|
name: "postgresql",
|
|
config: testhelpers.PostgresTestDatabase,
|
|
seedDB: seedDB,
|
|
},
|
|
{
|
|
name: "SQLite",
|
|
config: &database.Config{
|
|
Driver: database.DBSQLite3,
|
|
ConnectionDetails: drivers.ConnectionDetails{Database: "./testdb"},
|
|
},
|
|
seedDB: seedDB,
|
|
},
|
|
}
|
|
|
|
for x := range testCases {
|
|
test := testCases[x]
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if !testhelpers.CheckValidConfig(&test.config.ConnectionDetails) {
|
|
t.Skip("database not configured skipping test")
|
|
}
|
|
|
|
dbConn, err := testhelpers.ConnectToDatabase(test.config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if test.seedDB != nil {
|
|
err = test.seedDB(false)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
_, ohlcvData, err := genOHCLVData()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r, err := StoreInDatabase(&ohlcvData, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if r != 365 {
|
|
t.Fatalf("unexpected number inserted: %v", r)
|
|
}
|
|
|
|
r, err = StoreInDatabase(&ohlcvData, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if r != 365 {
|
|
t.Fatalf("unexpected number inserted: %v", r)
|
|
}
|
|
|
|
err = testhelpers.CloseDatabase(dbConn)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
})
|
|
}
|
|
|
|
err := os.RemoveAll(testhelpers.TempDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to remove temp db file: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadFromDatabase(t *testing.T) {
|
|
setupTest(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
config *database.Config
|
|
seedDB func(bool) error
|
|
runner func(t *testing.T)
|
|
closer func(dbConn *database.Instance) error
|
|
}{
|
|
{
|
|
name: "postgresql",
|
|
config: testhelpers.PostgresTestDatabase,
|
|
seedDB: seedDB,
|
|
},
|
|
{
|
|
name: "SQLite",
|
|
config: &database.Config{
|
|
Driver: database.DBSQLite3,
|
|
ConnectionDetails: drivers.ConnectionDetails{Database: "./testdb"},
|
|
},
|
|
seedDB: seedDB,
|
|
},
|
|
}
|
|
|
|
for x := range testCases {
|
|
test := testCases[x]
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if !testhelpers.CheckValidConfig(&test.config.ConnectionDetails) {
|
|
t.Skip("database not configured skipping test")
|
|
}
|
|
|
|
dbConn, err := testhelpers.ConnectToDatabase(test.config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if test.seedDB != nil {
|
|
err = test.seedDB(true)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
p, err := currency.NewPairFromString("BTCUSDT")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
start := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
end := start.AddDate(1, 0, 0)
|
|
ret, err := LoadFromDatabase(testExchanges[0].Name, p, asset.Spot, OneDay, start, end)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ret.Exchange != testExchanges[0].Name {
|
|
t.Fatalf("incorrect data returned: %v", ret.Exchange)
|
|
}
|
|
|
|
err = testhelpers.CloseDatabase(dbConn)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
})
|
|
}
|
|
|
|
err := os.RemoveAll(testhelpers.TempDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to remove temp db file: %v", err)
|
|
}
|
|
}
|
|
|
|
// TODO: find a better way to handle this to remove duplication between candle test
|
|
func seedDB(includeOHLCVData bool) error {
|
|
err := exchange.InsertMany(testExchanges)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if includeOHLCVData {
|
|
data, _, err := genOHCLVData()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = candle.Insert(&data)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func genOHCLVData() (out candle.Item, outItem Item, err error) {
|
|
exchangeUUID, err := exchange.UUIDByName(testExchanges[0].Name)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
out.ExchangeID = exchangeUUID.String()
|
|
out.Base = currency.BTC.String()
|
|
out.Quote = currency.USDT.String()
|
|
out.Interval = 86400
|
|
out.Asset = "spot"
|
|
|
|
start := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
for x := range 365 {
|
|
out.Candles = append(out.Candles, candle.Candle{
|
|
Timestamp: start.Add(time.Hour * 24 * time.Duration(x)),
|
|
Open: 1000,
|
|
High: 1000,
|
|
Low: 1000,
|
|
Close: 1000,
|
|
Volume: 1000,
|
|
})
|
|
}
|
|
|
|
outItem.Interval = OneDay
|
|
outItem.Asset = asset.Spot
|
|
outItem.Pair = currency.NewPair(currency.BTC, currency.USDT)
|
|
outItem.Exchange = testExchanges[0].Name
|
|
|
|
for x := range 365 {
|
|
outItem.Candles = append(outItem.Candles, Candle{
|
|
Time: start.Add(time.Hour * 24 * time.Duration(x)),
|
|
Open: 1000,
|
|
High: 1000,
|
|
Low: 1000,
|
|
Close: 1000,
|
|
Volume: 1000,
|
|
})
|
|
}
|
|
|
|
return out, outItem, nil
|
|
}
|
|
|
|
func TestLoadCSV(t *testing.T) {
|
|
v, err := LoadFromGCTScriptCSV(filepath.Join("..", "..", "testdata", "binance_BTCUSDT_24h_2019_01_01_2020_01_01.csv"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if v[0].Time.UTC() != time.Unix(1546300800, 0).UTC() {
|
|
t.Fatalf("unexpected value received: %v", v[0].Time)
|
|
}
|
|
|
|
if v[269].Close != 8177.91 {
|
|
t.Fatalf("unexpected value received: %v", v[269].Close)
|
|
}
|
|
|
|
if v[364].Open != 7246 {
|
|
t.Fatalf("unexpected value received: %v", v[364].Open)
|
|
}
|
|
}
|
|
|
|
func TestVerifyResultsHaveData(t *testing.T) {
|
|
t.Parallel()
|
|
tt1 := time.Now().Round(OneDay.Duration())
|
|
tt2 := tt1.Add(OneDay.Duration())
|
|
tt3 := tt2.Add(OneDay.Duration()) // end date no longer inclusive
|
|
dateRanges, err := CalculateCandleDateRanges(tt1, tt3, OneDay, 0)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if dateRanges.HasDataAtDate(tt1) {
|
|
t.Error("unexpected true value")
|
|
}
|
|
err = dateRanges.SetHasDataFromCandles([]Candle{
|
|
{
|
|
Time: tt1,
|
|
Low: 1337,
|
|
},
|
|
{
|
|
Time: tt2,
|
|
},
|
|
})
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if !dateRanges.HasDataAtDate(tt1) {
|
|
t.Error("expected true")
|
|
}
|
|
err = dateRanges.SetHasDataFromCandles([]Candle{
|
|
{
|
|
Time: tt1,
|
|
},
|
|
{
|
|
Time: tt2,
|
|
Low: 1337,
|
|
},
|
|
})
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if dateRanges.HasDataAtDate(tt1) {
|
|
t.Error("expected false")
|
|
}
|
|
}
|
|
|
|
func TestDataSummary(t *testing.T) {
|
|
t.Parallel()
|
|
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 !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
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 !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
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) {
|
|
t.Parallel()
|
|
tt1 := time.Now().Round(OneDay.Duration())
|
|
tt2 := tt1.Add(OneDay.Duration())
|
|
tt3 := tt2.Add(OneDay.Duration()) // end date no longer inclusive
|
|
dateRanges, err := CalculateCandleDateRanges(tt1, tt3, OneDay, 0)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if dateRanges.HasDataAtDate(tt2) {
|
|
t.Error("unexpected true value")
|
|
}
|
|
|
|
err = dateRanges.SetHasDataFromCandles([]Candle{
|
|
{
|
|
Time: tt1,
|
|
Close: 1337,
|
|
},
|
|
{
|
|
Time: tt2,
|
|
Close: 1337,
|
|
},
|
|
})
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if !dateRanges.HasDataAtDate(tt2) {
|
|
t.Error("unexpected false value")
|
|
}
|
|
|
|
if dateRanges.HasDataAtDate(tt2.Add(time.Hour * 24)) {
|
|
t.Error("should not have data")
|
|
}
|
|
}
|
|
|
|
func TestIntervalsPerYear(t *testing.T) {
|
|
t.Parallel()
|
|
var i Interval
|
|
if i.IntervalsPerYear() != 0 {
|
|
t.Error("expected 0")
|
|
}
|
|
i = OneYear
|
|
if i.IntervalsPerYear() != 1.0 {
|
|
t.Error("expected 1")
|
|
}
|
|
i = OneDay
|
|
if i.IntervalsPerYear() != 365 {
|
|
t.Error("expected 365")
|
|
}
|
|
i = OneHour
|
|
if i.IntervalsPerYear() != 8760 {
|
|
t.Error("expected 8670")
|
|
}
|
|
i = TwoHour
|
|
if i.IntervalsPerYear() != 4380 {
|
|
t.Error("expected 4380")
|
|
}
|
|
i = TwoHour + FifteenSecond
|
|
if i.IntervalsPerYear() != 4370.893970893971 {
|
|
t.Error("expected 4370...")
|
|
}
|
|
}
|
|
|
|
// The purpose of this benchmark is to highlight that requesting
|
|
// '.Unix()` frequently is a slow process
|
|
func BenchmarkJustifyIntervalTimeStoringUnixValues1(b *testing.B) {
|
|
tt1 := time.Now()
|
|
tt2 := time.Now().Add(-time.Hour)
|
|
tt3 := time.Now().Add(time.Hour)
|
|
for i := 0; i < b.N; i++ {
|
|
if tt1.Unix() == tt2.Unix() || (tt1.Unix() > tt2.Unix() && tt1.Unix() < tt3.Unix()) {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// The purpose of this benchmark is to highlight that storing the unix value
|
|
// at time of creation is dramatically faster than frequently requesting `.Unix()`
|
|
// at runtime at scale. When dealing with the backtester and comparing
|
|
// tens of thousands of candle times
|
|
func BenchmarkJustifyIntervalTimeStoringUnixValues2(b *testing.B) {
|
|
tt1 := time.Now().Unix()
|
|
tt2 := time.Now().Add(-time.Hour).Unix()
|
|
tt3 := time.Now().Add(time.Hour).Unix()
|
|
for i := 0; i < b.N; i++ {
|
|
if tt1 >= tt2 && tt1 <= tt3 {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConvertToNewInterval(t *testing.T) {
|
|
_, err := (*Item)(nil).ConvertToNewInterval(OneMin)
|
|
if !errors.Is(err, errNilKline) {
|
|
t.Errorf("received '%v' expected '%v'", err, errNilKline)
|
|
}
|
|
|
|
_, err = (&Item{}).ConvertToNewInterval(OneMin)
|
|
if !errors.Is(err, ErrInvalidInterval) {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrInvalidInterval)
|
|
}
|
|
|
|
old := &Item{
|
|
Exchange: "lol",
|
|
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
|
Asset: asset.Spot,
|
|
Interval: OneDay,
|
|
Candles: []Candle{
|
|
{
|
|
Time: time.Now(),
|
|
Open: 1337,
|
|
High: 1339,
|
|
Low: 1336,
|
|
Close: 1338,
|
|
Volume: 1337,
|
|
},
|
|
{
|
|
Time: time.Now().AddDate(0, 0, 1),
|
|
Open: 1338,
|
|
High: 2000,
|
|
Low: 1332,
|
|
Close: 1696,
|
|
Volume: 6420,
|
|
},
|
|
{
|
|
Time: time.Now().AddDate(0, 0, 2),
|
|
Open: 1696,
|
|
High: 1998,
|
|
Low: 1337,
|
|
Close: 6969,
|
|
Volume: 2520,
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = old.ConvertToNewInterval(0)
|
|
if !errors.Is(err, ErrInvalidInterval) {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrInvalidInterval)
|
|
}
|
|
_, err = old.ConvertToNewInterval(OneMin)
|
|
if !errors.Is(err, ErrCanOnlyUpscaleCandles) {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrCanOnlyUpscaleCandles)
|
|
}
|
|
old.Interval = ThreeDay
|
|
_, err = old.ConvertToNewInterval(OneWeek)
|
|
if !errors.Is(err, ErrWholeNumberScaling) {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrWholeNumberScaling)
|
|
}
|
|
|
|
old.Interval = OneDay
|
|
newInterval := ThreeDay
|
|
newCandle, err := old.ConvertToNewInterval(newInterval)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if len(newCandle.Candles) != 1 {
|
|
t.Error("expected one candle")
|
|
}
|
|
if newCandle.Candles[0].Open != 1337 &&
|
|
newCandle.Candles[0].High != 2000 &&
|
|
newCandle.Candles[0].Low != 1332 &&
|
|
newCandle.Candles[0].Close != 6969 &&
|
|
newCandle.Candles[0].Volume != (2520+6420+1337) {
|
|
t.Error("unexpected updoot")
|
|
}
|
|
|
|
old.Candles = append(old.Candles, Candle{
|
|
Time: time.Now().AddDate(0, 0, 3),
|
|
Open: 6969,
|
|
High: 1998,
|
|
Low: 2342,
|
|
Close: 7777,
|
|
Volume: 111,
|
|
})
|
|
newCandle, err = old.ConvertToNewInterval(newInterval)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if len(newCandle.Candles) != 1 {
|
|
t.Error("expected one candle")
|
|
}
|
|
|
|
_, err = old.ConvertToNewInterval(OneMonth)
|
|
if !errors.Is(err, ErrInsufficientCandleData) {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrInsufficientCandleData)
|
|
}
|
|
|
|
tn := time.Now().Truncate(time.Duration(OneDay))
|
|
|
|
// Test incorrectly padded candles
|
|
old.Candles = []Candle{
|
|
{
|
|
Time: tn,
|
|
Open: 1337,
|
|
High: 1339,
|
|
Low: 1336,
|
|
Close: 1338,
|
|
Volume: 1337,
|
|
},
|
|
{
|
|
Time: tn.AddDate(0, 0, 1),
|
|
Open: 1338,
|
|
High: 2000,
|
|
Low: 1332,
|
|
Close: 1696,
|
|
Volume: 6420,
|
|
},
|
|
{
|
|
Time: tn.AddDate(0, 0, 2),
|
|
Open: 1696,
|
|
High: 1998,
|
|
Low: 1337,
|
|
Close: 6969,
|
|
Volume: 2520,
|
|
},
|
|
// empty candle should be here <---
|
|
// aaaand empty candle should be here <---
|
|
{
|
|
Time: tn.AddDate(0, 0, 5),
|
|
Open: 6969,
|
|
High: 8888,
|
|
Low: 1111,
|
|
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)
|
|
if !errors.Is(err, errCandleDataNotPadded) {
|
|
t.Errorf("received '%v' expected '%v'", err, errCandleDataNotPadded)
|
|
}
|
|
|
|
err = old.addPadding(tn, tn.AddDate(0, 0, 9), false)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
newCandle, err = old.ConvertToNewInterval(newInterval)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if len(newCandle.Candles) != 3 {
|
|
t.Errorf("received '%v' expected '%v'", len(newCandle.Candles), 3)
|
|
}
|
|
}
|
|
|
|
func TestAddPadding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tn := time.Now().Truncate(time.Duration(OneDay))
|
|
|
|
var k *Item
|
|
err := k.addPadding(tn, tn.AddDate(0, 0, 5), false)
|
|
if !errors.Is(err, errNilKline) {
|
|
t.Fatalf("received '%v' expected '%v'", err, errNilKline)
|
|
}
|
|
|
|
k = &Item{}
|
|
k.Candles = []Candle{
|
|
{
|
|
Time: tn,
|
|
Open: 1337,
|
|
High: 1339,
|
|
Low: 1336,
|
|
Close: 1338,
|
|
Volume: 1337,
|
|
},
|
|
}
|
|
err = k.addPadding(tn, tn.AddDate(0, 0, 5), false)
|
|
if !errors.Is(err, ErrInvalidInterval) {
|
|
t.Fatalf("received '%v' expected '%v'", err, ErrInvalidInterval)
|
|
}
|
|
|
|
k.Interval = OneDay
|
|
k.Candles = []Candle{
|
|
{
|
|
Time: tn.AddDate(0, 0, 1),
|
|
Open: 1338,
|
|
High: 2000,
|
|
Low: 1332,
|
|
Close: 1696,
|
|
Volume: 6420,
|
|
},
|
|
{
|
|
Time: tn,
|
|
Open: 1337,
|
|
High: 1339,
|
|
Low: 1336,
|
|
Close: 1338,
|
|
Volume: 1337,
|
|
},
|
|
}
|
|
err = k.addPadding(tn.AddDate(0, 0, 5), tn, false)
|
|
if !errors.Is(err, errCannotEstablishTimeWindow) {
|
|
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,
|
|
Open: 1337,
|
|
High: 1339,
|
|
Low: 1336,
|
|
Close: 1338,
|
|
Volume: 1337,
|
|
},
|
|
{
|
|
Time: tn.AddDate(0, 0, 1),
|
|
Open: 1338,
|
|
High: 2000,
|
|
Low: 1332,
|
|
Close: 1696,
|
|
Volume: 6420,
|
|
},
|
|
{
|
|
Time: tn.AddDate(0, 0, 2),
|
|
Open: 1696,
|
|
High: 1998,
|
|
Low: 1337,
|
|
Close: 6969,
|
|
Volume: 2520,
|
|
}}
|
|
|
|
err = k.addPadding(tn, tn.AddDate(0, 0, 3), false)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if len(k.Candles) != 3 {
|
|
t.Fatalf("received '%v' expected '%v'", len(k.Candles), 3)
|
|
}
|
|
|
|
k.Candles = append(k.Candles, Candle{
|
|
Time: tn.AddDate(0, 0, 5),
|
|
Open: 6969,
|
|
High: 8888,
|
|
Low: 1111,
|
|
Close: 5555,
|
|
Volume: 2520,
|
|
})
|
|
|
|
err = k.addPadding(tn, tn.AddDate(0, 0, 6), false)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if len(k.Candles) != 6 {
|
|
t.Fatalf("received '%v' expected '%v'", len(k.Candles), 6)
|
|
}
|
|
|
|
// No candles test when there is zero activity for that period
|
|
k.Candles = nil
|
|
|
|
err = k.addPadding(tn, tn.AddDate(0, 0, 6), false)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if len(k.Candles) != 6 {
|
|
t.Errorf("received '%v' expected '%v'", len(k.Candles), 6)
|
|
}
|
|
}
|
|
|
|
func TestGetClosePriceAtTime(t *testing.T) {
|
|
t.Parallel()
|
|
tt := time.Now()
|
|
k := Item{
|
|
Candles: []Candle{
|
|
{
|
|
Time: tt,
|
|
Close: 1337,
|
|
},
|
|
{
|
|
Time: tt.Add(time.Hour),
|
|
Close: 1338,
|
|
},
|
|
},
|
|
}
|
|
price, err := k.GetClosePriceAtTime(tt)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if price != 1337 {
|
|
t.Errorf("received '%v' expected '%v'", price, 1337)
|
|
}
|
|
_, err = k.GetClosePriceAtTime(tt.Add(time.Minute))
|
|
if !errors.Is(err, ErrNotFoundAtTime) {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrNotFoundAtTime)
|
|
}
|
|
}
|
|
|
|
func TestDeployExchangeIntervals(t *testing.T) {
|
|
t.Parallel()
|
|
exchangeIntervals := DeployExchangeIntervals()
|
|
if exchangeIntervals.ExchangeSupported(OneWeek) {
|
|
t.Errorf("received '%v' expected '%v'", exchangeIntervals.ExchangeSupported(OneWeek), false)
|
|
}
|
|
|
|
exchangeIntervals = DeployExchangeIntervals(IntervalCapacity{Interval: OneWeek})
|
|
if !exchangeIntervals.ExchangeSupported(OneWeek) {
|
|
t.Errorf("received '%v' expected '%v'", exchangeIntervals.ExchangeSupported(OneWeek), true)
|
|
}
|
|
|
|
_, err := exchangeIntervals.Construct(0)
|
|
if !errors.Is(err, ErrInvalidInterval) {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrInvalidInterval)
|
|
}
|
|
|
|
_, err = exchangeIntervals.Construct(OneMin)
|
|
if !errors.Is(err, ErrCannotConstructInterval) {
|
|
t.Errorf("received '%v' expected '%v'", err, ErrCannotConstructInterval)
|
|
}
|
|
|
|
request, err := exchangeIntervals.Construct(OneWeek)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if request != OneWeek {
|
|
t.Errorf("received '%v' expected '%v'", request, OneWeek)
|
|
}
|
|
|
|
exchangeIntervals = DeployExchangeIntervals(IntervalCapacity{Interval: OneWeek}, IntervalCapacity{Interval: OneDay})
|
|
|
|
request, err = exchangeIntervals.Construct(OneMonth)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
if request != OneDay {
|
|
t.Errorf("received '%v' expected '%v'", request, OneDay)
|
|
}
|
|
}
|
|
|
|
func TestSetHasDataFromCandles(t *testing.T) {
|
|
t.Parallel()
|
|
ohc := getOneHour()
|
|
localEnd := ohc[len(ohc)-1].Time.Add(OneHour.Duration())
|
|
i, err := CalculateCandleDateRanges(ohc[0].Time, localEnd, OneHour, 100000)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
err = i.SetHasDataFromCandles(ohc)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if !i.Start.Equal(ohc[0].Time) {
|
|
t.Errorf("received '%v' expected '%v'", i.Start.Time, ohc[0].Time)
|
|
}
|
|
if !i.End.Equal(localEnd) {
|
|
t.Errorf("received '%v' expected '%v'", i.End.Time, ohc[len(ohc)-1].Time)
|
|
}
|
|
|
|
k := Item{
|
|
Interval: OneHour,
|
|
Candles: ohc[2:],
|
|
}
|
|
err = k.addPadding(i.Start.Time, i.End.Time, false)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
|
|
err = i.SetHasDataFromCandles(k.Candles)
|
|
if !errors.Is(err, nil) {
|
|
t.Errorf("received '%v' expected '%v'", err, nil)
|
|
}
|
|
if !i.Start.Equal(k.Candles[0].Time) {
|
|
t.Errorf("received '%v' expected '%v'", i.Start.Time, k.Candles[0].Time)
|
|
}
|
|
if i.HasDataAtDate(k.Candles[0].Time) {
|
|
t.Errorf("received '%v' expected '%v'", false, true)
|
|
}
|
|
if !i.HasDataAtDate(k.Candles[len(k.Candles)-1].Time) {
|
|
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)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalJSON(t *testing.T) {
|
|
t.Parallel()
|
|
var i Interval
|
|
for _, tt := range []struct {
|
|
in string
|
|
exp Interval
|
|
}{{`"3m"`, ThreeMin}, {`"15s"`, FifteenSecond}, {`720000000000`, OneMin * 12}, {`"-1ns"`, Raw}, {`"raw"`, Raw}} {
|
|
err := i.UnmarshalJSON([]byte(tt.in))
|
|
assert.NoErrorf(t, err, "UnmarshalJSON should not error on %q", tt.in)
|
|
}
|
|
err := i.UnmarshalJSON([]byte(`"6hedgehogs"`))
|
|
assert.ErrorContains(t, err, "unknown unit", "UnmarshalJSON should error")
|
|
}
|