mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-04 15:10:54 +00:00
* golangci-lint/CI: Bump versions
Fix remaining linter issues
* Specifically set AppVeyor version
* Fix the infamous typos 👀
* Add go env cmd to AppVeyor
* Add go version cmd to AppVeyor
* Specify AppVeyor image, adjust linters
* Update go get to go install due to deprecation
* Bump golangci-lint timeout time for AppVeyor
* Change NW contract to NQ
* Address nitters
* GetRandomPair -> Pair{}
* Address nits
* Address time nitterinos plus additional tweaks
* More time inception upgrades!
* Bending time and space
148 lines
4.0 KiB
Go
148 lines
4.0 KiB
Go
package timeperiods
|
|
|
|
import (
|
|
"errors"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
)
|
|
|
|
// FindTimeRangesContainingData will break the start and end into time periods using the provided period
|
|
// it will then check whether any comparisonTimes are within those periods and concatenate them
|
|
// eg if no comparisonTimes match, you will receive 1 TimeRange of Start End with dataInRange = false
|
|
// eg2 if 1 comparisonTime matches in the middle of start and end, you will receive three ranges
|
|
func FindTimeRangesContainingData(start, end time.Time, period time.Duration, comparisonTimes []time.Time) ([]TimeRange, error) {
|
|
var errs common.Errors
|
|
if start.IsZero() {
|
|
errs = append(errs, errors.New("invalid start time"))
|
|
}
|
|
if end.IsZero() {
|
|
errs = append(errs, errors.New("invalid end time"))
|
|
}
|
|
if err := validatePeriod(period); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
if len(errs) > 0 {
|
|
return nil, errs
|
|
}
|
|
var t TimePeriodCalculator
|
|
t.periodDuration = period
|
|
t.start = start.Truncate(period)
|
|
t.end = end.Truncate(period)
|
|
t.comparisonTimes = comparisonTimes
|
|
|
|
t.setTimePeriodExists()
|
|
t.Sort(false)
|
|
t.calculateRanges()
|
|
|
|
return t.TimeRanges, nil
|
|
}
|
|
|
|
func validatePeriod(period time.Duration) error {
|
|
if period != time.Hour &&
|
|
period != time.Second &&
|
|
period != time.Minute &&
|
|
period != time.Hour*24 {
|
|
return errors.New("invalid period")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CalculateTimePeriodsInRange can break down start and end times into time periods
|
|
// eg 1 hourly intervals
|
|
func CalculateTimePeriodsInRange(start, end time.Time, period time.Duration) ([]TimePeriod, error) {
|
|
var errs common.Errors
|
|
if start.IsZero() {
|
|
errs = append(errs, errors.New("invalid start time"))
|
|
}
|
|
if end.IsZero() {
|
|
errs = append(errs, errors.New("invalid end time"))
|
|
}
|
|
if err := validatePeriod(period); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
if len(errs) > 0 {
|
|
return nil, errs
|
|
}
|
|
|
|
var t TimePeriodCalculator
|
|
t.periodDuration = period
|
|
t.start = start.Truncate(period)
|
|
t.end = end.Truncate(period)
|
|
|
|
t.calculatePeriods()
|
|
|
|
return t.TimePeriods, nil
|
|
}
|
|
|
|
func (t *TimePeriodCalculator) calculateRanges() {
|
|
var tr TimeRange
|
|
for i := range t.TimePeriods {
|
|
if i != 0 {
|
|
if (t.TimePeriods[i].dataInRange && !t.TimePeriods[i-1].dataInRange) ||
|
|
(!t.TimePeriods[i].dataInRange && t.TimePeriods[i-1].dataInRange) {
|
|
// the status has changed and therefore a range has ended
|
|
tr.HasDataInRange = t.TimePeriods[i-1].dataInRange
|
|
tr.EndOfRange = t.TimePeriods[i].Time
|
|
t.TimeRanges = append(t.TimeRanges, tr)
|
|
tr = TimeRange{}
|
|
}
|
|
}
|
|
if tr.StartOfRange.IsZero() {
|
|
// start of new time range
|
|
tr.StartOfRange = t.TimePeriods[i].Time
|
|
}
|
|
}
|
|
if !tr.StartOfRange.IsZero() {
|
|
if tr.EndOfRange.IsZero() {
|
|
tr.EndOfRange = t.end
|
|
}
|
|
tr.HasDataInRange = t.TimePeriods[len(t.TimePeriods)-1].dataInRange
|
|
t.TimeRanges = append(t.TimeRanges, tr)
|
|
}
|
|
}
|
|
|
|
func (t *TimePeriodCalculator) calculatePeriods() {
|
|
if t.start.IsZero() || t.end.IsZero() {
|
|
return
|
|
}
|
|
if t.start.After(t.end) {
|
|
return
|
|
}
|
|
iterateDateMate := t.start
|
|
for !iterateDateMate.Equal(t.end) && !iterateDateMate.After(t.end) {
|
|
tp := TimePeriod{
|
|
Time: iterateDateMate,
|
|
dataInRange: false,
|
|
}
|
|
t.TimePeriods = append(t.TimePeriods, tp)
|
|
iterateDateMate = iterateDateMate.Add(t.periodDuration)
|
|
}
|
|
}
|
|
|
|
// setTimePeriodExists compares loaded comparisonTimes
|
|
// against calculated TimePeriods to determine whether
|
|
// there is existing data within the time period
|
|
func (t *TimePeriodCalculator) setTimePeriodExists() {
|
|
t.calculatePeriods()
|
|
for i := range t.TimePeriods {
|
|
for j := range t.comparisonTimes {
|
|
if t.comparisonTimes[j].Truncate(t.periodDuration).Equal(t.TimePeriods[i].Time) {
|
|
t.TimePeriods[i].dataInRange = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort will sort the time period asc or desc
|
|
func (t *TimePeriodCalculator) Sort(desc bool) {
|
|
sort.Slice(t.TimePeriods, func(i, j int) bool {
|
|
if desc {
|
|
return t.TimePeriods[i].Time.After(t.TimePeriods[j].Time)
|
|
}
|
|
return t.TimePeriods[i].Time.Before(t.TimePeriods[j].Time)
|
|
})
|
|
}
|