sequence tweaks, removing market hours anything
This commit is contained in:
parent
1a09989055
commit
0e849b11bb
17 changed files with 413 additions and 877 deletions
11
Makefile
11
Makefile
|
@ -1,5 +1,9 @@
|
||||||
all: test
|
all: test
|
||||||
|
|
||||||
|
tools:
|
||||||
|
@go get -u github.com/blend/go-sdk/_bin/coverage
|
||||||
|
@go get -u github.com/blend/go-sdk/_bin/profanity
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test ./...
|
@go test ./...
|
||||||
|
|
||||||
|
@ -8,9 +12,4 @@ profanity:
|
||||||
@profanity -include="*.go,Makefile,README.md"
|
@profanity -include="*.go,Makefile,README.md"
|
||||||
|
|
||||||
cover:
|
cover:
|
||||||
@go test -short -covermode=set -coverprofile=profile.cov
|
@coverage
|
||||||
@go tool cover -html=profile.cov
|
|
||||||
@rm profile.cov
|
|
||||||
|
|
||||||
deps:
|
|
||||||
@go get -u github.com/blend/go-sdk/_bin/profanity
|
|
42
linear_coefficient_provider.go
Normal file
42
linear_coefficient_provider.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
// LinearCoefficientProvider is a type that returns linear cofficients.
|
||||||
|
type LinearCoefficientProvider interface {
|
||||||
|
Coefficients() (m, b, stdev, avg float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinearCoefficients returns a fixed linear coefficient pair.
|
||||||
|
func LinearCoefficients(m, b float64) LinearCoefficientSet {
|
||||||
|
return LinearCoefficientSet{
|
||||||
|
M: m,
|
||||||
|
B: b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizedLinearCoefficients returns a fixed linear coefficient pair.
|
||||||
|
func NormalizedLinearCoefficients(m, b, stdev, avg float64) LinearCoefficientSet {
|
||||||
|
return LinearCoefficientSet{
|
||||||
|
M: m,
|
||||||
|
B: b,
|
||||||
|
StdDev: stdev,
|
||||||
|
Avg: avg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinearCoefficientSet is the m and b values for the linear equation in the form:
|
||||||
|
// y = (m*x) + b
|
||||||
|
type LinearCoefficientSet struct {
|
||||||
|
M float64
|
||||||
|
B float64
|
||||||
|
StdDev float64
|
||||||
|
Avg float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coefficients returns the coefficients.
|
||||||
|
func (lcs LinearCoefficientSet) Coefficients() (m, b, stdev, avg float64) {
|
||||||
|
m = lcs.M
|
||||||
|
b = lcs.B
|
||||||
|
stdev = lcs.StdDev
|
||||||
|
avg = lcs.Avg
|
||||||
|
return
|
||||||
|
}
|
|
@ -9,9 +9,10 @@ import (
|
||||||
|
|
||||||
// Interface Assertions.
|
// Interface Assertions.
|
||||||
var (
|
var (
|
||||||
_ Series = (*LinearRegressionSeries)(nil)
|
_ Series = (*LinearRegressionSeries)(nil)
|
||||||
_ FirstValuesProvider = (*LinearRegressionSeries)(nil)
|
_ FirstValuesProvider = (*LinearRegressionSeries)(nil)
|
||||||
_ LastValuesProvider = (*LinearRegressionSeries)(nil)
|
_ LastValuesProvider = (*LinearRegressionSeries)(nil)
|
||||||
|
_ LinearCoefficientProvider = (*LinearRegressionSeries)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinearRegressionSeries is a series that plots the n-nearest neighbors
|
// LinearRegressionSeries is a series that plots the n-nearest neighbors
|
||||||
|
@ -31,6 +32,19 @@ type LinearRegressionSeries struct {
|
||||||
stddevx float64
|
stddevx float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Coefficients returns the linear coefficients for the series.
|
||||||
|
func (lrs LinearRegressionSeries) Coefficients() (m, b, stdev, avg float64) {
|
||||||
|
if lrs.IsZero() {
|
||||||
|
lrs.computeCoefficients()
|
||||||
|
}
|
||||||
|
|
||||||
|
m = lrs.m
|
||||||
|
b = lrs.b
|
||||||
|
stdev = lrs.stddevx
|
||||||
|
avg = lrs.avgx
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
// GetName returns the name of the time series.
|
||||||
func (lrs LinearRegressionSeries) GetName() string {
|
func (lrs LinearRegressionSeries) GetName() string {
|
||||||
return lrs.Name
|
return lrs.Name
|
||||||
|
@ -79,7 +93,7 @@ func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
|
||||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if lrs.m == 0 && lrs.b == 0 {
|
if lrs.IsZero() {
|
||||||
lrs.computeCoefficients()
|
lrs.computeCoefficients()
|
||||||
}
|
}
|
||||||
offset := lrs.GetOffset()
|
offset := lrs.GetOffset()
|
||||||
|
@ -94,7 +108,7 @@ func (lrs *LinearRegressionSeries) GetFirstValues() (x, y float64) {
|
||||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if lrs.m == 0 && lrs.b == 0 {
|
if lrs.IsZero() {
|
||||||
lrs.computeCoefficients()
|
lrs.computeCoefficients()
|
||||||
}
|
}
|
||||||
x, y = lrs.InnerSeries.GetValues(0)
|
x, y = lrs.InnerSeries.GetValues(0)
|
||||||
|
@ -107,7 +121,7 @@ func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
|
||||||
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if lrs.m == 0 && lrs.b == 0 {
|
if lrs.IsZero() {
|
||||||
lrs.computeCoefficients()
|
lrs.computeCoefficients()
|
||||||
}
|
}
|
||||||
endIndex := lrs.GetEndIndex()
|
endIndex := lrs.GetEndIndex()
|
||||||
|
@ -116,6 +130,29 @@ func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render renders the series.
|
||||||
|
func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
|
style := lrs.Style.InheritFrom(defaults)
|
||||||
|
Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the series.
|
||||||
|
func (lrs *LinearRegressionSeries) Validate() error {
|
||||||
|
if lrs.InnerSeries == nil {
|
||||||
|
return fmt.Errorf("linear regression series requires InnerSeries to be set")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns if we've computed the coefficients or not.
|
||||||
|
func (lrs *LinearRegressionSeries) IsZero() bool {
|
||||||
|
return lrs.m == 0 && lrs.b == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// internal helpers
|
||||||
|
//
|
||||||
|
|
||||||
func (lrs *LinearRegressionSeries) normalize(xvalue float64) float64 {
|
func (lrs *LinearRegressionSeries) normalize(xvalue float64) float64 {
|
||||||
return (xvalue - lrs.avgx) / lrs.stddevx
|
return (xvalue - lrs.avgx) / lrs.stddevx
|
||||||
}
|
}
|
||||||
|
@ -151,17 +188,3 @@ func (lrs *LinearRegressionSeries) computeCoefficients() {
|
||||||
lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx)
|
lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx)
|
||||||
lrs.b = (sumy / p) - (lrs.m * sumx / p)
|
lrs.b = (sumy / p) - (lrs.m * sumx / p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders the series.
|
|
||||||
func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
|
||||||
style := lrs.Style.InheritFrom(defaults)
|
|
||||||
Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates the series.
|
|
||||||
func (lrs *LinearRegressionSeries) Validate() error {
|
|
||||||
if lrs.InnerSeries == nil {
|
|
||||||
return fmt.Errorf("linear regression series requires InnerSeries to be set")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
119
linear_series.go
Normal file
119
linear_series.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface Assertions.
|
||||||
|
var (
|
||||||
|
_ Series = (*LinearSeries)(nil)
|
||||||
|
_ FirstValuesProvider = (*LinearSeries)(nil)
|
||||||
|
_ LastValuesProvider = (*LinearSeries)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinearSeries is a series that plots a line in a given domain.
|
||||||
|
type LinearSeries struct {
|
||||||
|
Name string
|
||||||
|
Style Style
|
||||||
|
YAxis YAxisType
|
||||||
|
|
||||||
|
XValues []float64
|
||||||
|
InnerSeries LinearCoefficientProvider
|
||||||
|
|
||||||
|
m float64
|
||||||
|
b float64
|
||||||
|
stdev float64
|
||||||
|
avg float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name of the time series.
|
||||||
|
func (ls LinearSeries) GetName() string {
|
||||||
|
return ls.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStyle returns the line style.
|
||||||
|
func (ls LinearSeries) GetStyle() Style {
|
||||||
|
return ls.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
|
func (ls LinearSeries) GetYAxis() YAxisType {
|
||||||
|
return ls.YAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in the series.
|
||||||
|
func (ls LinearSeries) Len() int {
|
||||||
|
return len(ls.XValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEndIndex returns the effective limit end.
|
||||||
|
func (ls LinearSeries) GetEndIndex() int {
|
||||||
|
return len(ls.XValues) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValues gets a value at a given index.
|
||||||
|
func (ls *LinearSeries) GetValues(index int) (x, y float64) {
|
||||||
|
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ls.IsZero() {
|
||||||
|
ls.computeCoefficients()
|
||||||
|
}
|
||||||
|
x = ls.XValues[index]
|
||||||
|
y = (ls.m * ls.normalize(x)) + ls.b
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFirstValues computes the first linear regression value.
|
||||||
|
func (ls *LinearSeries) GetFirstValues() (x, y float64) {
|
||||||
|
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ls.IsZero() {
|
||||||
|
ls.computeCoefficients()
|
||||||
|
}
|
||||||
|
x, y = ls.GetValues(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLastValues computes the last linear regression value.
|
||||||
|
func (ls *LinearSeries) GetLastValues() (x, y float64) {
|
||||||
|
if ls.InnerSeries == nil || len(ls.XValues) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ls.IsZero() {
|
||||||
|
ls.computeCoefficients()
|
||||||
|
}
|
||||||
|
x, y = ls.GetValues(ls.GetEndIndex())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render renders the series.
|
||||||
|
func (ls *LinearSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
|
Draw.LineSeries(r, canvasBox, xrange, yrange, ls.Style.InheritFrom(defaults), ls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the series.
|
||||||
|
func (ls LinearSeries) Validate() error {
|
||||||
|
if ls.InnerSeries == nil {
|
||||||
|
return fmt.Errorf("linear regression series requires InnerSeries to be set")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns if the linear series has computed coefficients or not.
|
||||||
|
func (ls LinearSeries) IsZero() bool {
|
||||||
|
return ls.m == 0 && ls.b == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`.
|
||||||
|
func (ls *LinearSeries) computeCoefficients() {
|
||||||
|
ls.m, ls.b, ls.stdev, ls.avg = ls.InnerSeries.Coefficients()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinearSeries) normalize(xvalue float64) float64 {
|
||||||
|
if ls.avg > 0 && ls.stdev > 0 {
|
||||||
|
return (xvalue - ls.avg) / ls.stdev
|
||||||
|
}
|
||||||
|
return xvalue
|
||||||
|
}
|
|
@ -1,195 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/seq"
|
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MarketHoursRange is a special type of range that compresses a time range into just the
|
|
||||||
// market (i.e. NYSE operating hours and days) range.
|
|
||||||
type MarketHoursRange struct {
|
|
||||||
Min time.Time
|
|
||||||
Max time.Time
|
|
||||||
|
|
||||||
MarketOpen time.Time
|
|
||||||
MarketClose time.Time
|
|
||||||
|
|
||||||
HolidayProvider util.HolidayProvider
|
|
||||||
|
|
||||||
ValueFormatter ValueFormatter
|
|
||||||
|
|
||||||
Descending bool
|
|
||||||
Domain int
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDescending returns if the range is descending.
|
|
||||||
func (mhr MarketHoursRange) IsDescending() bool {
|
|
||||||
return mhr.Descending
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTimezone returns the timezone for the market hours range.
|
|
||||||
func (mhr MarketHoursRange) GetTimezone() *time.Location {
|
|
||||||
return mhr.GetMarketOpen().Location()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsZero returns if the range is setup or not.
|
|
||||||
func (mhr MarketHoursRange) IsZero() bool {
|
|
||||||
return mhr.Min.IsZero() && mhr.Max.IsZero()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMin returns the min value.
|
|
||||||
func (mhr MarketHoursRange) GetMin() float64 {
|
|
||||||
return util.Time.ToFloat64(mhr.Min)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMax returns the max value.
|
|
||||||
func (mhr MarketHoursRange) GetMax() float64 {
|
|
||||||
return util.Time.ToFloat64(mhr.GetEffectiveMax())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEffectiveMax gets either the close on the max, or the max itself.
|
|
||||||
func (mhr MarketHoursRange) GetEffectiveMax() time.Time {
|
|
||||||
maxClose := util.Date.On(mhr.MarketClose, mhr.Max)
|
|
||||||
if maxClose.After(mhr.Max) {
|
|
||||||
return maxClose
|
|
||||||
}
|
|
||||||
return mhr.Max
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMin sets the min value.
|
|
||||||
func (mhr *MarketHoursRange) SetMin(min float64) {
|
|
||||||
mhr.Min = util.Time.FromFloat64(min)
|
|
||||||
mhr.Min = mhr.Min.In(mhr.GetTimezone())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMax sets the max value.
|
|
||||||
func (mhr *MarketHoursRange) SetMax(max float64) {
|
|
||||||
mhr.Max = util.Time.FromFloat64(max)
|
|
||||||
mhr.Max = mhr.Max.In(mhr.GetTimezone())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDelta gets the delta.
|
|
||||||
func (mhr MarketHoursRange) GetDelta() float64 {
|
|
||||||
min := mhr.GetMin()
|
|
||||||
max := mhr.GetMax()
|
|
||||||
return max - min
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDomain gets the domain.
|
|
||||||
func (mhr MarketHoursRange) GetDomain() int {
|
|
||||||
return mhr.Domain
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDomain sets the domain.
|
|
||||||
func (mhr *MarketHoursRange) SetDomain(domain int) {
|
|
||||||
mhr.Domain = domain
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHolidayProvider coalesces a userprovided holiday provider and the date.DefaultHolidayProvider.
|
|
||||||
func (mhr MarketHoursRange) GetHolidayProvider() util.HolidayProvider {
|
|
||||||
if mhr.HolidayProvider == nil {
|
|
||||||
return func(_ time.Time) bool { return false }
|
|
||||||
}
|
|
||||||
return mhr.HolidayProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMarketOpen returns the market open time.
|
|
||||||
func (mhr MarketHoursRange) GetMarketOpen() time.Time {
|
|
||||||
if mhr.MarketOpen.IsZero() {
|
|
||||||
return util.NYSEOpen()
|
|
||||||
}
|
|
||||||
return mhr.MarketOpen
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMarketClose returns the market close time.
|
|
||||||
func (mhr MarketHoursRange) GetMarketClose() time.Time {
|
|
||||||
if mhr.MarketClose.IsZero() {
|
|
||||||
return util.NYSEClose()
|
|
||||||
}
|
|
||||||
return mhr.MarketClose
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTicks returns the ticks for the range.
|
|
||||||
// This is to override the default continous ticks that would be generated for the range.
|
|
||||||
func (mhr *MarketHoursRange) GetTicks(r Renderer, defaults Style, vf ValueFormatter) []Tick {
|
|
||||||
times := seq.Time.MarketHours(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
|
||||||
timesWidth := mhr.measureTimes(r, defaults, vf, times)
|
|
||||||
if timesWidth <= mhr.Domain {
|
|
||||||
return mhr.makeTicks(vf, times)
|
|
||||||
}
|
|
||||||
|
|
||||||
times = seq.Time.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
|
||||||
if timesWidth <= mhr.Domain {
|
|
||||||
return mhr.makeTicks(vf, times)
|
|
||||||
}
|
|
||||||
|
|
||||||
times = seq.Time.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
|
||||||
if timesWidth <= mhr.Domain {
|
|
||||||
return mhr.makeTicks(vf, times)
|
|
||||||
}
|
|
||||||
|
|
||||||
times = seq.Time.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
|
||||||
if timesWidth <= mhr.Domain {
|
|
||||||
return mhr.makeTicks(vf, times)
|
|
||||||
}
|
|
||||||
|
|
||||||
times = seq.Time.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
|
||||||
if timesWidth <= mhr.Domain {
|
|
||||||
return mhr.makeTicks(vf, times)
|
|
||||||
}
|
|
||||||
|
|
||||||
return GenerateContinuousTicks(r, mhr, false, defaults, vf)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mhr *MarketHoursRange) measureTimes(r Renderer, defaults Style, vf ValueFormatter, times []time.Time) int {
|
|
||||||
defaults.GetTextOptions().WriteToRenderer(r)
|
|
||||||
var total int
|
|
||||||
for index, t := range times {
|
|
||||||
timeLabel := vf(t)
|
|
||||||
|
|
||||||
labelBox := r.MeasureText(timeLabel)
|
|
||||||
total += labelBox.Width()
|
|
||||||
if index > 0 {
|
|
||||||
total += DefaultMinimumTickHorizontalSpacing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mhr *MarketHoursRange) makeTicks(vf ValueFormatter, times []time.Time) []Tick {
|
|
||||||
ticks := make([]Tick, len(times))
|
|
||||||
for index, t := range times {
|
|
||||||
ticks[index] = Tick{
|
|
||||||
Value: util.Time.ToFloat64(t),
|
|
||||||
Label: vf(t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ticks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mhr MarketHoursRange) String() string {
|
|
||||||
return fmt.Sprintf("MarketHoursRange [%s, %s] => %d", mhr.Min.Format(time.RFC3339), mhr.Max.Format(time.RFC3339), mhr.Domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate maps a given value into the ContinuousRange space.
|
|
||||||
func (mhr MarketHoursRange) Translate(value float64) int {
|
|
||||||
valueTime := util.Time.FromFloat64(value)
|
|
||||||
valueTimeEastern := valueTime.In(util.Date.Eastern())
|
|
||||||
totalSeconds := util.Date.CalculateMarketSecondsBetween(mhr.Min, mhr.GetEffectiveMax(), mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
|
||||||
valueDelta := util.Date.CalculateMarketSecondsBetween(mhr.Min, valueTimeEastern, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.HolidayProvider)
|
|
||||||
translated := int((float64(valueDelta) / float64(totalSeconds)) * float64(mhr.Domain))
|
|
||||||
|
|
||||||
if mhr.IsDescending() {
|
|
||||||
return mhr.Domain - translated
|
|
||||||
}
|
|
||||||
|
|
||||||
return translated
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
assert "github.com/blend/go-sdk/assert"
|
|
||||||
"github.com/wcharczuk/go-chart/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMarketHoursRangeGetDelta(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
r := &MarketHoursRange{
|
|
||||||
Min: time.Date(2016, 07, 19, 9, 30, 0, 0, util.Date.Eastern()),
|
|
||||||
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, util.Date.Eastern()),
|
|
||||||
MarketOpen: util.NYSEOpen(),
|
|
||||||
MarketClose: util.NYSEClose(),
|
|
||||||
HolidayProvider: util.Date.IsNYSEHoliday,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NotZero(r.GetDelta())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarketHoursRangeTranslate(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
r := &MarketHoursRange{
|
|
||||||
Min: time.Date(2016, 07, 18, 9, 30, 0, 0, util.Date.Eastern()),
|
|
||||||
Max: time.Date(2016, 07, 22, 16, 00, 0, 0, util.Date.Eastern()),
|
|
||||||
MarketOpen: util.NYSEOpen(),
|
|
||||||
MarketClose: util.NYSEClose(),
|
|
||||||
HolidayProvider: util.Date.IsNYSEHoliday,
|
|
||||||
Domain: 1000,
|
|
||||||
}
|
|
||||||
|
|
||||||
weds := time.Date(2016, 07, 20, 9, 30, 0, 0, util.Date.Eastern())
|
|
||||||
|
|
||||||
assert.Equal(0, r.Translate(util.Time.ToFloat64(r.Min)))
|
|
||||||
assert.Equal(400, r.Translate(util.Time.ToFloat64(weds)))
|
|
||||||
assert.Equal(1000, r.Translate(util.Time.ToFloat64(r.Max)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarketHoursRangeGetTicks(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
r, err := PNG(1024, 1024)
|
|
||||||
assert.Nil(err)
|
|
||||||
|
|
||||||
f, err := GetDefaultFont()
|
|
||||||
assert.Nil(err)
|
|
||||||
|
|
||||||
defaults := Style{
|
|
||||||
Font: f,
|
|
||||||
FontSize: 10,
|
|
||||||
FontColor: ColorBlack,
|
|
||||||
}
|
|
||||||
|
|
||||||
ra := &MarketHoursRange{
|
|
||||||
Min: util.Date.On(util.NYSEOpen(), util.Date.Date(2016, 07, 18, util.Date.Eastern())),
|
|
||||||
Max: util.Date.On(util.NYSEClose(), util.Date.Date(2016, 07, 22, util.Date.Eastern())),
|
|
||||||
MarketOpen: util.NYSEOpen(),
|
|
||||||
MarketClose: util.NYSEClose(),
|
|
||||||
HolidayProvider: util.Date.IsNYSEHoliday,
|
|
||||||
Domain: 1024,
|
|
||||||
}
|
|
||||||
|
|
||||||
ticks := ra.GetTicks(r, defaults, TimeValueFormatter)
|
|
||||||
assert.NotEmpty(ticks)
|
|
||||||
assert.Len(ticks, 5)
|
|
||||||
assert.NotEqual(util.Time.ToFloat64(ra.Min), ticks[0].Value)
|
|
||||||
assert.NotEmpty(ticks[0].Label)
|
|
||||||
}
|
|
127
seq/time.go
127
seq/time.go
|
@ -20,95 +20,6 @@ func (ts timeSequence) Days(days int) []time.Time {
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts timeSequence) MarketHours(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := util.Date.On(marketOpen, from)
|
|
||||||
toClose := util.Date.On(marketClose, to)
|
|
||||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
|
||||||
todayOpen := util.Date.On(marketOpen, cursor)
|
|
||||||
todayClose := util.Date.On(marketClose, cursor)
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
|
||||||
|
|
||||||
if (cursor.Equal(todayOpen) || cursor.After(todayOpen)) && (cursor.Equal(todayClose) || cursor.Before(todayClose)) && isValidTradingDay {
|
|
||||||
times = append(times, cursor)
|
|
||||||
}
|
|
||||||
if cursor.After(todayClose) {
|
|
||||||
cursor = util.Date.NextMarketOpen(cursor, marketOpen, isHoliday)
|
|
||||||
} else {
|
|
||||||
cursor = util.Date.NextHour(cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSequence) MarketHourQuarters(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := util.Date.On(marketOpen, from)
|
|
||||||
toClose := util.Date.On(marketClose, to)
|
|
||||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
|
||||||
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
|
||||||
|
|
||||||
if isValidTradingDay {
|
|
||||||
todayOpen := util.Date.On(marketOpen, cursor)
|
|
||||||
todayNoon := util.Date.NoonOn(cursor)
|
|
||||||
today2pm := util.Date.On(util.Date.Time(14, 0, 0, 0, cursor.Location()), cursor)
|
|
||||||
todayClose := util.Date.On(marketClose, cursor)
|
|
||||||
times = append(times, todayOpen, todayNoon, today2pm, todayClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = util.Date.NextDay(cursor)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSequence) MarketDayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := util.Date.On(marketOpen, from)
|
|
||||||
toClose := util.Date.On(marketClose, to)
|
|
||||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
|
||||||
if isValidTradingDay {
|
|
||||||
todayClose := util.Date.On(marketClose, cursor)
|
|
||||||
times = append(times, todayClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = util.Date.NextDay(cursor)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSequence) MarketDayAlternateCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := util.Date.On(marketOpen, from)
|
|
||||||
toClose := util.Date.On(marketClose, to)
|
|
||||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
|
||||||
if isValidTradingDay {
|
|
||||||
todayClose := util.Date.On(marketClose, cursor)
|
|
||||||
times = append(times, todayClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = cursor.AddDate(0, 0, 2)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSequence) MarketDayMondayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday util.HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := util.Date.On(marketClose, from)
|
|
||||||
toClose := util.Date.On(marketClose, to)
|
|
||||||
|
|
||||||
for cursor.Equal(toClose) || cursor.Before(toClose) {
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && util.Date.IsWeekDay(cursor.Weekday())
|
|
||||||
if isValidTradingDay {
|
|
||||||
times = append(times, cursor)
|
|
||||||
}
|
|
||||||
cursor = util.Date.NextDayOfWeek(cursor, time.Monday)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSequence) Hours(start time.Time, totalHours int) []time.Time {
|
func (ts timeSequence) Hours(start time.Time, totalHours int) []time.Time {
|
||||||
times := make([]time.Time, totalHours)
|
times := make([]time.Time, totalHours)
|
||||||
|
|
||||||
|
@ -123,49 +34,17 @@ func (ts timeSequence) Hours(start time.Time, totalHours int) []time.Time {
|
||||||
|
|
||||||
// HoursFilled adds zero values for the data bounded by the start and end of the xdata array.
|
// HoursFilled adds zero values for the data bounded by the start and end of the xdata array.
|
||||||
func (ts timeSequence) HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
func (ts timeSequence) HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
||||||
start := Time.Start(xdata)
|
start, end := util.Time.StartAndEnd(xdata...)
|
||||||
end := Time.End(xdata)
|
totalHours := util.Time.DiffHours(start, end)
|
||||||
|
|
||||||
totalHours := util.Math.AbsInt(util.Date.DiffHours(start, end))
|
|
||||||
|
|
||||||
finalTimes := ts.Hours(start, totalHours+1)
|
finalTimes := ts.Hours(start, totalHours+1)
|
||||||
finalValues := make([]float64, totalHours+1)
|
finalValues := make([]float64, totalHours+1)
|
||||||
|
|
||||||
var hoursFromStart int
|
var hoursFromStart int
|
||||||
for i, xd := range xdata {
|
for i, xd := range xdata {
|
||||||
hoursFromStart = util.Date.DiffHours(start, xd)
|
hoursFromStart = util.Time.DiffHours(start, xd)
|
||||||
finalValues[hoursFromStart] = ydata[i]
|
finalValues[hoursFromStart] = ydata[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalTimes, finalValues
|
return finalTimes, finalValues
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start returns the earliest (min) time in a list of times.
|
|
||||||
func (ts timeSequence) Start(times []time.Time) time.Time {
|
|
||||||
if len(times) == 0 {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
start := times[0]
|
|
||||||
for _, t := range times[1:] {
|
|
||||||
if t.Before(start) {
|
|
||||||
start = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return start
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start returns the earliest (min) time in a list of times.
|
|
||||||
func (ts timeSequence) End(times []time.Time) time.Time {
|
|
||||||
if len(times) == 0 {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
end := times[0]
|
|
||||||
for _, t := range times[1:] {
|
|
||||||
if t.After(end) {
|
|
||||||
end = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return end
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,40 +8,13 @@ import (
|
||||||
"github.com/wcharczuk/go-chart/util"
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTimeMarketHours(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, util.Date.Eastern())
|
|
||||||
mh := Time.MarketHours(today, today, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
|
||||||
assert.Len(mh, 8)
|
|
||||||
assert.Equal(util.Date.Eastern(), mh[0].Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeMarketHourQuarters(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, util.Date.Eastern())
|
|
||||||
mh := Time.MarketHourQuarters(today, today, util.NYSEOpen(), util.NYSEClose(), util.Date.IsNYSEHoliday)
|
|
||||||
assert.Len(mh, 4)
|
|
||||||
assert.Equal(9, mh[0].Hour())
|
|
||||||
assert.Equal(30, mh[0].Minute())
|
|
||||||
assert.Equal(util.Date.Eastern(), mh[0].Location())
|
|
||||||
|
|
||||||
assert.Equal(12, mh[1].Hour())
|
|
||||||
assert.Equal(00, mh[1].Minute())
|
|
||||||
assert.Equal(util.Date.Eastern(), mh[1].Location())
|
|
||||||
|
|
||||||
assert.Equal(14, mh[2].Hour())
|
|
||||||
assert.Equal(00, mh[2].Minute())
|
|
||||||
assert.Equal(util.Date.Eastern(), mh[2].Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeHours(t *testing.T) {
|
func TestTimeHours(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
today := time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC)
|
today := time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC)
|
||||||
seq := Time.Hours(today, 24)
|
seq := Time.Hours(today, 24)
|
||||||
|
|
||||||
end := Time.End(seq)
|
end := util.Time.End(seq...)
|
||||||
assert.Len(seq, 24)
|
assert.Len(seq, 24)
|
||||||
assert.Equal(2016, end.Year())
|
assert.Equal(2016, end.Year())
|
||||||
assert.Equal(07, int(end.Month()))
|
assert.Equal(07, int(end.Month()))
|
||||||
|
@ -73,7 +46,7 @@ func TestSequenceHoursFill(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
filledTimes, filledValues := Time.HoursFilled(xdata, ydata)
|
filledTimes, filledValues := Time.HoursFilled(xdata, ydata)
|
||||||
expected := util.Date.DiffHours(Time.Start(xdata), Time.End(xdata)) + 1
|
expected := util.Time.DiffHours(util.Time.Start(xdata...), util.Time.End(xdata...)) + 1
|
||||||
assert.Len(filledTimes, expected)
|
assert.Len(filledTimes, expected)
|
||||||
assert.Equal(len(filledValues), len(filledTimes))
|
assert.Equal(len(filledValues), len(filledTimes))
|
||||||
|
|
||||||
|
@ -94,7 +67,7 @@ func TestTimeStart(t *testing.T) {
|
||||||
time.Now().AddDate(0, 0, -5),
|
time.Now().AddDate(0, 0, -5),
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.InTimeDelta(Time.Start(times), times[4], time.Millisecond)
|
assert.InTimeDelta(util.Time.Start(times...), times[4], time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimeEnd(t *testing.T) {
|
func TestTimeEnd(t *testing.T) {
|
||||||
|
@ -108,5 +81,5 @@ func TestTimeEnd(t *testing.T) {
|
||||||
time.Now().AddDate(0, 0, -5),
|
time.Now().AddDate(0, 0, -5),
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.InTimeDelta(Time.End(times), times[2], time.Millisecond)
|
assert.InTimeDelta(util.Time.End(times...), times[2], time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
31
seq/times.go
Normal file
31
seq/times.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package seq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert types implement interfaces.
|
||||||
|
var (
|
||||||
|
_ Provider = (*Times)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Times are an array of times.
|
||||||
|
// It wraps the array with methods that implement `seq.Provider`.
|
||||||
|
type Times []time.Time
|
||||||
|
|
||||||
|
// Array returns the times to an array.
|
||||||
|
func (t Times) Array() []time.Time {
|
||||||
|
return []time.Time(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the array.
|
||||||
|
func (t Times) Len() int {
|
||||||
|
return len(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue returns a value at an index as a time.
|
||||||
|
func (t Times) GetValue(index int) float64 {
|
||||||
|
return util.Time.ToFloat64(t[index])
|
||||||
|
}
|
322
util/date.go
322
util/date.go
|
@ -1,7 +1,6 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,192 +44,88 @@ var (
|
||||||
Epoch = time.Unix(0, 0)
|
Epoch = time.Unix(0, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// Date contains utility functions that operate on dates.
|
||||||
_easternLock sync.Mutex
|
var Date date
|
||||||
_eastern *time.Location
|
|
||||||
)
|
|
||||||
|
|
||||||
// NYSEOpen is when the NYSE opens.
|
|
||||||
func NYSEOpen() time.Time { return Date.Time(9, 30, 0, 0, Date.Eastern()) }
|
|
||||||
|
|
||||||
// NYSEClose is when the NYSE closes.
|
|
||||||
func NYSEClose() time.Time { return Date.Time(16, 0, 0, 0, Date.Eastern()) }
|
|
||||||
|
|
||||||
// NASDAQOpen is when NASDAQ opens.
|
|
||||||
func NASDAQOpen() time.Time { return Date.Time(9, 30, 0, 0, Date.Eastern()) }
|
|
||||||
|
|
||||||
// NASDAQClose is when NASDAQ closes.
|
|
||||||
func NASDAQClose() time.Time { return Date.Time(16, 0, 0, 0, Date.Eastern()) }
|
|
||||||
|
|
||||||
// NYSEArcaOpen is when NYSEARCA opens.
|
|
||||||
func NYSEArcaOpen() time.Time { return Date.Time(4, 0, 0, 0, Date.Eastern()) }
|
|
||||||
|
|
||||||
// NYSEArcaClose is when NYSEARCA closes.
|
|
||||||
func NYSEArcaClose() time.Time { return Date.Time(20, 0, 0, 0, Date.Eastern()) }
|
|
||||||
|
|
||||||
// HolidayProvider is a function that returns if a given time falls on a holiday.
|
|
||||||
type HolidayProvider func(time.Time) bool
|
|
||||||
|
|
||||||
// defaultHolidayProvider implements `HolidayProvider` and just returns false.
|
|
||||||
func defaultHolidayProvider(_ time.Time) bool { return false }
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Date contains utility functions that operate on dates.
|
|
||||||
Date = &date{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type date struct{}
|
type date struct{}
|
||||||
|
|
||||||
// IsNYSEHoliday returns if a date was/is on a nyse holiday day.
|
func (d date) MustEastern() *time.Location {
|
||||||
func (d date) IsNYSEHoliday(t time.Time) bool {
|
if eastern, err := d.Eastern(); err != nil {
|
||||||
te := t.In(d.Eastern())
|
panic(err)
|
||||||
if te.Year() == 2013 {
|
} else {
|
||||||
if te.Month() == 1 {
|
return eastern
|
||||||
return te.Day() == 1 || te.Day() == 21
|
}
|
||||||
} else if te.Month() == 2 {
|
}
|
||||||
return te.Day() == 18
|
|
||||||
} else if te.Month() == 3 {
|
// Eastern returns the eastern timezone.
|
||||||
return te.Day() == 29
|
func (d date) Eastern() (*time.Location, error) {
|
||||||
} else if te.Month() == 5 {
|
// Try POSIX
|
||||||
return te.Day() == 27
|
est, err := time.LoadLocation("America/New_York")
|
||||||
} else if te.Month() == 7 {
|
if err == nil {
|
||||||
return te.Day() == 4
|
// Try Windows
|
||||||
} else if te.Month() == 9 {
|
est, err = time.LoadLocation("EST")
|
||||||
return te.Day() == 2
|
if err == nil {
|
||||||
} else if te.Month() == 11 {
|
return nil, err
|
||||||
return te.Day() == 28
|
|
||||||
} else if te.Month() == 12 {
|
|
||||||
return te.Day() == 25
|
|
||||||
}
|
|
||||||
} else if te.Year() == 2014 {
|
|
||||||
if te.Month() == 1 {
|
|
||||||
return te.Day() == 1 || te.Day() == 20
|
|
||||||
} else if te.Month() == 2 {
|
|
||||||
return te.Day() == 17
|
|
||||||
} else if te.Month() == 4 {
|
|
||||||
return te.Day() == 18
|
|
||||||
} else if te.Month() == 5 {
|
|
||||||
return te.Day() == 26
|
|
||||||
} else if te.Month() == 7 {
|
|
||||||
return te.Day() == 4
|
|
||||||
} else if te.Month() == 9 {
|
|
||||||
return te.Day() == 1
|
|
||||||
} else if te.Month() == 11 {
|
|
||||||
return te.Day() == 27
|
|
||||||
} else if te.Month() == 12 {
|
|
||||||
return te.Day() == 25
|
|
||||||
}
|
|
||||||
} else if te.Year() == 2015 {
|
|
||||||
if te.Month() == 1 {
|
|
||||||
return te.Day() == 1 || te.Day() == 19
|
|
||||||
} else if te.Month() == 2 {
|
|
||||||
return te.Day() == 16
|
|
||||||
} else if te.Month() == 4 {
|
|
||||||
return te.Day() == 3
|
|
||||||
} else if te.Month() == 5 {
|
|
||||||
return te.Day() == 25
|
|
||||||
} else if te.Month() == 7 {
|
|
||||||
return te.Day() == 3
|
|
||||||
} else if te.Month() == 9 {
|
|
||||||
return te.Day() == 7
|
|
||||||
} else if te.Month() == 11 {
|
|
||||||
return te.Day() == 26
|
|
||||||
} else if te.Month() == 12 {
|
|
||||||
return te.Day() == 25
|
|
||||||
}
|
|
||||||
} else if te.Year() == 2016 {
|
|
||||||
if te.Month() == 1 {
|
|
||||||
return te.Day() == 1 || te.Day() == 18
|
|
||||||
} else if te.Month() == 2 {
|
|
||||||
return te.Day() == 15
|
|
||||||
} else if te.Month() == 3 {
|
|
||||||
return te.Day() == 25
|
|
||||||
} else if te.Month() == 5 {
|
|
||||||
return te.Day() == 30
|
|
||||||
} else if te.Month() == 7 {
|
|
||||||
return te.Day() == 4
|
|
||||||
} else if te.Month() == 9 {
|
|
||||||
return te.Day() == 5
|
|
||||||
} else if te.Month() == 11 {
|
|
||||||
return te.Day() == 24 || te.Day() == 25
|
|
||||||
} else if te.Month() == 12 {
|
|
||||||
return te.Day() == 26
|
|
||||||
}
|
|
||||||
} else if te.Year() == 2017 {
|
|
||||||
if te.Month() == 1 {
|
|
||||||
return te.Day() == 1 || te.Day() == 16
|
|
||||||
} else if te.Month() == 2 {
|
|
||||||
return te.Day() == 20
|
|
||||||
} else if te.Month() == 4 {
|
|
||||||
return te.Day() == 15
|
|
||||||
} else if te.Month() == 5 {
|
|
||||||
return te.Day() == 29
|
|
||||||
} else if te.Month() == 7 {
|
|
||||||
return te.Day() == 4
|
|
||||||
} else if te.Month() == 9 {
|
|
||||||
return te.Day() == 4
|
|
||||||
} else if te.Month() == 11 {
|
|
||||||
return te.Day() == 23
|
|
||||||
} else if te.Month() == 12 {
|
|
||||||
return te.Day() == 25
|
|
||||||
}
|
|
||||||
} else if te.Year() == 2018 {
|
|
||||||
if te.Month() == 1 {
|
|
||||||
return te.Day() == 1 || te.Day() == 15
|
|
||||||
} else if te.Month() == 2 {
|
|
||||||
return te.Day() == 19
|
|
||||||
} else if te.Month() == 3 {
|
|
||||||
return te.Day() == 30
|
|
||||||
} else if te.Month() == 5 {
|
|
||||||
return te.Day() == 28
|
|
||||||
} else if te.Month() == 7 {
|
|
||||||
return te.Day() == 4
|
|
||||||
} else if te.Month() == 9 {
|
|
||||||
return te.Day() == 3
|
|
||||||
} else if te.Month() == 11 {
|
|
||||||
return te.Day() == 22
|
|
||||||
} else if te.Month() == 12 {
|
|
||||||
return te.Day() == 25
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return est, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNYSEArcaHoliday returns that returns if a given time falls on a holiday.
|
func (d date) MustPacific() *time.Location {
|
||||||
func (d date) IsNYSEArcaHoliday(t time.Time) bool {
|
if pst, err := d.Pacific(); err != nil {
|
||||||
return d.IsNYSEHoliday(t)
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return pst
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNASDAQHoliday returns if a date was a NASDAQ holiday day.
|
// Pacific returns the pacific timezone.
|
||||||
func (d date) IsNASDAQHoliday(t time.Time) bool {
|
func (d date) Pacific() (*time.Location, error) {
|
||||||
return d.IsNYSEHoliday(t)
|
// Try POSIX
|
||||||
|
pst, err := time.LoadLocation("America/Los_Angeles")
|
||||||
|
if err == nil {
|
||||||
|
// Try Windows
|
||||||
|
pst, err = time.LoadLocation("PST")
|
||||||
|
if err == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeUTC returns a new time.Time for the given clock components in UTC.
|
||||||
|
// It is meant to be used with the `OnDate` function.
|
||||||
|
func (d date) TimeUTC(hour, min, sec, nsec int) time.Time {
|
||||||
|
return time.Date(0, 0, 0, hour, min, sec, nsec, time.UTC)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time returns a new time.Time for the given clock components.
|
// Time returns a new time.Time for the given clock components.
|
||||||
|
// It is meant to be used with the `OnDate` function.
|
||||||
func (d date) Time(hour, min, sec, nsec int, loc *time.Location) time.Time {
|
func (d date) Time(hour, min, sec, nsec int, loc *time.Location) time.Time {
|
||||||
return time.Date(0, 0, 0, hour, min, sec, nsec, loc)
|
return time.Date(0, 0, 0, hour, min, sec, nsec, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DateUTC returns a new time.Time for the given date comonents at (noon) in UTC.
|
||||||
|
func (d date) DateUTC(year, month, day int) time.Time {
|
||||||
|
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateUTC returns a new time.Time for the given date comonents at (noon) in a given location.
|
||||||
func (d date) Date(year, month, day int, loc *time.Location) time.Time {
|
func (d date) Date(year, month, day int, loc *time.Location) time.Time {
|
||||||
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, loc)
|
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// On returns the clock components of clock (hour,minute,second) on the date components of d.
|
// OnDate returns the clock components of clock (hour,minute,second) on the date components of d.
|
||||||
func (d date) On(clock, cd time.Time) time.Time {
|
func (d date) OnDate(clock, date time.Time) time.Time {
|
||||||
tzAdjusted := cd.In(clock.Location())
|
tzAdjusted := date.In(clock.Location())
|
||||||
return time.Date(tzAdjusted.Year(), tzAdjusted.Month(), tzAdjusted.Day(), clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(), clock.Location())
|
return time.Date(tzAdjusted.Year(), tzAdjusted.Month(), tzAdjusted.Day(), clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(), clock.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoonOn is a shortcut for On(Time(12,0,0), cd) a.k.a. noon on a given date.
|
// NoonOnDate is a shortcut for On(Time(12,0,0), cd) a.k.a. noon on a given date.
|
||||||
func (d date) NoonOn(cd time.Time) time.Time {
|
func (d date) NoonOnDate(cd time.Time) time.Time {
|
||||||
return time.Date(cd.Year(), cd.Month(), cd.Day(), 12, 0, 0, 0, cd.Location())
|
return time.Date(cd.Year(), cd.Month(), cd.Day(), 12, 0, 0, 0, cd.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional returns a pointer reference to a given time.
|
|
||||||
func (d date) Optional(t time.Time) *time.Time {
|
|
||||||
return &t
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWeekDay returns if the day is a monday->friday.
|
// IsWeekDay returns if the day is a monday->friday.
|
||||||
func (d date) IsWeekDay(day time.Weekday) bool {
|
func (d date) IsWeekDay(day time.Weekday) bool {
|
||||||
return !d.IsWeekendDay(day)
|
return !d.IsWeekendDay(day)
|
||||||
|
@ -253,116 +148,11 @@ func (d date) Before(before, reference time.Time) bool {
|
||||||
return tzAdjustedBefore.Year() == reference.Year() && tzAdjustedBefore.Month() == reference.Month() && tzAdjustedBefore.Day() < reference.Day()
|
return tzAdjustedBefore.Year() == reference.Year() && tzAdjustedBefore.Month() == reference.Month() && tzAdjustedBefore.Day() < reference.Day()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextMarketOpen returns the next market open after a given time.
|
|
||||||
func (d date) NextMarketOpen(after, openTime time.Time, isHoliday HolidayProvider) time.Time {
|
|
||||||
afterLocalized := after.In(openTime.Location())
|
|
||||||
todaysOpen := d.On(openTime, afterLocalized)
|
|
||||||
|
|
||||||
if isHoliday == nil {
|
|
||||||
isHoliday = defaultHolidayProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
todayIsValidTradingDay := d.IsWeekDay(todaysOpen.Weekday()) && !isHoliday(todaysOpen)
|
|
||||||
|
|
||||||
if (afterLocalized.Equal(todaysOpen) || afterLocalized.Before(todaysOpen)) && todayIsValidTradingDay {
|
|
||||||
return todaysOpen
|
|
||||||
}
|
|
||||||
|
|
||||||
for cursorDay := 1; cursorDay < 7; cursorDay++ {
|
|
||||||
newDay := todaysOpen.AddDate(0, 0, cursorDay)
|
|
||||||
isValidTradingDay := d.IsWeekDay(newDay.Weekday()) && !isHoliday(newDay)
|
|
||||||
if isValidTradingDay {
|
|
||||||
return d.On(openTime, newDay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("Have exhausted day window looking for next market open.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextMarketClose returns the next market close after a given time.
|
|
||||||
func (d date) NextMarketClose(after, closeTime time.Time, isHoliday HolidayProvider) time.Time {
|
|
||||||
afterLocalized := after.In(closeTime.Location())
|
|
||||||
|
|
||||||
if isHoliday == nil {
|
|
||||||
isHoliday = defaultHolidayProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
todaysClose := d.On(closeTime, afterLocalized)
|
|
||||||
if afterLocalized.Before(todaysClose) && d.IsWeekDay(todaysClose.Weekday()) && !isHoliday(todaysClose) {
|
|
||||||
return todaysClose
|
|
||||||
}
|
|
||||||
|
|
||||||
if afterLocalized.Equal(todaysClose) { //rare but it might happen.
|
|
||||||
return todaysClose
|
|
||||||
}
|
|
||||||
|
|
||||||
for cursorDay := 1; cursorDay < 6; cursorDay++ {
|
|
||||||
newDay := todaysClose.AddDate(0, 0, cursorDay)
|
|
||||||
if d.IsWeekDay(newDay.Weekday()) && !isHoliday(newDay) {
|
|
||||||
return d.On(closeTime, newDay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("Have exhausted day window looking for next market close.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalculateMarketSecondsBetween calculates the number of seconds the market was open between two dates.
|
|
||||||
func (d date) CalculateMarketSecondsBetween(start, end, marketOpen, marketClose time.Time, isHoliday HolidayProvider) (seconds int64) {
|
|
||||||
startEastern := start.In(d.Eastern())
|
|
||||||
endEastern := end.In(d.Eastern())
|
|
||||||
|
|
||||||
startMarketOpen := d.On(marketOpen, startEastern)
|
|
||||||
startMarketClose := d.On(marketClose, startEastern)
|
|
||||||
|
|
||||||
if !d.IsWeekendDay(startMarketOpen.Weekday()) && !isHoliday(startMarketOpen) {
|
|
||||||
if (startEastern.Equal(startMarketOpen) || startEastern.After(startMarketOpen)) && startEastern.Before(startMarketClose) {
|
|
||||||
if endEastern.Before(startMarketClose) {
|
|
||||||
seconds += int64(endEastern.Sub(startEastern) / time.Second)
|
|
||||||
} else {
|
|
||||||
seconds += int64(startMarketClose.Sub(startEastern) / time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor := d.NextMarketOpen(startMarketClose, marketOpen, isHoliday)
|
|
||||||
for d.Before(cursor, endEastern) {
|
|
||||||
if d.IsWeekDay(cursor.Weekday()) && !isHoliday(cursor) {
|
|
||||||
close := d.NextMarketClose(cursor, marketClose, isHoliday)
|
|
||||||
seconds += int64(close.Sub(cursor) / time.Second)
|
|
||||||
}
|
|
||||||
cursor = cursor.AddDate(0, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
finalMarketOpen := d.NextMarketOpen(cursor, marketOpen, isHoliday)
|
|
||||||
finalMarketClose := d.NextMarketClose(cursor, marketClose, isHoliday)
|
|
||||||
if endEastern.After(finalMarketOpen) {
|
|
||||||
if endEastern.Before(finalMarketClose) {
|
|
||||||
seconds += int64(endEastern.Sub(finalMarketOpen) / time.Second)
|
|
||||||
} else {
|
|
||||||
seconds += int64(finalMarketClose.Sub(finalMarketOpen) / time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_secondsPerHour = 60 * 60
|
_secondsPerHour = 60 * 60
|
||||||
_secondsPerDay = 60 * 60 * 24
|
_secondsPerDay = 60 * 60 * 24
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d date) DiffDays(t1, t2 time.Time) (days int) {
|
|
||||||
t1n := t1.Unix()
|
|
||||||
t2n := t2.Unix()
|
|
||||||
diff := t2n - t1n //yields seconds
|
|
||||||
return int(diff / (_secondsPerDay))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d date) DiffHours(t1, t2 time.Time) (hours int) {
|
|
||||||
t1n := t1.Unix()
|
|
||||||
t2n := t2.Unix()
|
|
||||||
diff := t2n - t1n //yields seconds
|
|
||||||
return int(diff / (_secondsPerHour))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextDay returns the timestamp advanced a day.
|
// NextDay returns the timestamp advanced a day.
|
||||||
func (d date) NextDay(ts time.Time) time.Time {
|
func (d date) NextDay(ts time.Time) time.Time {
|
||||||
return ts.AddDate(0, 0, 1)
|
return ts.AddDate(0, 0, 1)
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Eastern returns the eastern timezone.
|
|
||||||
func (d date) Eastern() *time.Location {
|
|
||||||
if _eastern == nil {
|
|
||||||
_easternLock.Lock()
|
|
||||||
defer _easternLock.Unlock()
|
|
||||||
if _eastern == nil {
|
|
||||||
_eastern, _ = time.LoadLocation("America/New_York")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _eastern
|
|
||||||
}
|
|
|
@ -33,10 +33,10 @@ func TestDateDate(t *testing.T) {
|
||||||
assert.Equal(time.UTC, ts.Location())
|
assert.Equal(time.UTC, ts.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateOn(t *testing.T) {
|
func TestDateOnDate(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
ts := Date.On(Date.Time(5, 4, 3, 2, time.UTC), Date.Date(2016, 6, 7, Date.Eastern()))
|
ts := Date.OnDate(Date.Time(5, 4, 3, 2, time.UTC), Date.Date(2016, 6, 7, Date.MustEastern()))
|
||||||
assert.Equal(2016, ts.Year())
|
assert.Equal(2016, ts.Year())
|
||||||
assert.Equal(6, ts.Month())
|
assert.Equal(6, ts.Month())
|
||||||
assert.Equal(7, ts.Day())
|
assert.Equal(7, ts.Day())
|
||||||
|
@ -47,9 +47,9 @@ func TestDateOn(t *testing.T) {
|
||||||
assert.Equal(time.UTC, ts.Location())
|
assert.Equal(time.UTC, ts.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateNoonOn(t *testing.T) {
|
func TestDateNoonOnDate(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
noon := Date.NoonOn(time.Date(2016, 04, 03, 02, 01, 0, 0, time.UTC))
|
noon := Date.NoonOnDate(time.Date(2016, 04, 03, 02, 01, 0, 0, time.UTC))
|
||||||
|
|
||||||
assert.Equal(2016, noon.Year())
|
assert.Equal(2016, noon.Year())
|
||||||
assert.Equal(4, noon.Month())
|
assert.Equal(4, noon.Month())
|
||||||
|
@ -77,101 +77,16 @@ func TestDateBeforeHandlesTimezones(t *testing.T) {
|
||||||
|
|
||||||
tuesdayUTC := time.Date(2016, 8, 02, 22, 00, 0, 0, time.UTC)
|
tuesdayUTC := time.Date(2016, 8, 02, 22, 00, 0, 0, time.UTC)
|
||||||
mondayUTC := time.Date(2016, 8, 01, 1, 00, 0, 0, time.UTC)
|
mondayUTC := time.Date(2016, 8, 01, 1, 00, 0, 0, time.UTC)
|
||||||
sundayEST := time.Date(2016, 7, 31, 22, 00, 0, 0, Date.Eastern())
|
sundayEST := time.Date(2016, 7, 31, 22, 00, 0, 0, Date.MustEastern())
|
||||||
|
|
||||||
assert.True(Date.Before(sundayEST, tuesdayUTC))
|
assert.True(Date.Before(sundayEST, tuesdayUTC))
|
||||||
assert.False(Date.Before(sundayEST, mondayUTC))
|
assert.False(Date.Before(sundayEST, mondayUTC))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNextMarketOpen(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
beforeOpen := time.Date(2016, 07, 18, 9, 0, 0, 0, Date.Eastern())
|
|
||||||
todayOpen := time.Date(2016, 07, 18, 9, 30, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
afterOpen := time.Date(2016, 07, 18, 9, 31, 0, 0, Date.Eastern())
|
|
||||||
tomorrowOpen := time.Date(2016, 07, 19, 9, 30, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
afterFriday := time.Date(2016, 07, 22, 9, 31, 0, 0, Date.Eastern())
|
|
||||||
mondayOpen := time.Date(2016, 07, 25, 9, 30, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
assert.True(todayOpen.Equal(Date.NextMarketOpen(beforeOpen, NYSEOpen(), Date.IsNYSEHoliday)))
|
|
||||||
assert.True(tomorrowOpen.Equal(Date.NextMarketOpen(afterOpen, NYSEOpen(), Date.IsNYSEHoliday)))
|
|
||||||
assert.True(mondayOpen.Equal(Date.NextMarketOpen(afterFriday, NYSEOpen(), Date.IsNYSEHoliday)))
|
|
||||||
assert.True(mondayOpen.Equal(Date.NextMarketOpen(weekend, NYSEOpen(), Date.IsNYSEHoliday)))
|
|
||||||
|
|
||||||
assert.Equal(Date.Eastern(), todayOpen.Location())
|
|
||||||
assert.Equal(Date.Eastern(), tomorrowOpen.Location())
|
|
||||||
assert.Equal(Date.Eastern(), mondayOpen.Location())
|
|
||||||
|
|
||||||
testRegression := time.Date(2016, 07, 18, 16, 0, 0, 0, Date.Eastern())
|
|
||||||
shouldbe := time.Date(2016, 07, 19, 9, 30, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
assert.True(shouldbe.Equal(Date.NextMarketOpen(testRegression, NYSEOpen(), Date.IsNYSEHoliday)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNextMarketClose(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
beforeClose := time.Date(2016, 07, 18, 15, 0, 0, 0, Date.Eastern())
|
|
||||||
todayClose := time.Date(2016, 07, 18, 16, 00, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
afterClose := time.Date(2016, 07, 18, 16, 1, 0, 0, Date.Eastern())
|
|
||||||
tomorrowClose := time.Date(2016, 07, 19, 16, 00, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
afterFriday := time.Date(2016, 07, 22, 16, 1, 0, 0, Date.Eastern())
|
|
||||||
mondayClose := time.Date(2016, 07, 25, 16, 0, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
assert.True(todayClose.Equal(Date.NextMarketClose(beforeClose, NYSEClose(), Date.IsNYSEHoliday)))
|
|
||||||
assert.True(tomorrowClose.Equal(Date.NextMarketClose(afterClose, NYSEClose(), Date.IsNYSEHoliday)))
|
|
||||||
assert.True(mondayClose.Equal(Date.NextMarketClose(afterFriday, NYSEClose(), Date.IsNYSEHoliday)))
|
|
||||||
assert.True(mondayClose.Equal(Date.NextMarketClose(weekend, NYSEClose(), Date.IsNYSEHoliday)))
|
|
||||||
|
|
||||||
assert.Equal(Date.Eastern(), todayClose.Location())
|
|
||||||
assert.Equal(Date.Eastern(), tomorrowClose.Location())
|
|
||||||
assert.Equal(Date.Eastern(), mondayClose.Location())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalculateMarketSecondsBetween(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
start := time.Date(2016, 07, 18, 9, 30, 0, 0, Date.Eastern())
|
|
||||||
end := time.Date(2016, 07, 22, 16, 00, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
shouldbe := 5 * 6.5 * 60 * 60
|
|
||||||
|
|
||||||
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalculateMarketSecondsBetween1D(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
start := time.Date(2016, 07, 22, 9, 45, 0, 0, Date.Eastern())
|
|
||||||
end := time.Date(2016, 07, 22, 15, 45, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
shouldbe := 6 * 60 * 60
|
|
||||||
|
|
||||||
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalculateMarketSecondsBetweenLTM(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
|
||||||
end := time.Date(2016, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
|
||||||
|
|
||||||
shouldbe := 253 * 6.5 * 60 * 60 //253 full market days since this date last year.
|
|
||||||
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateNextHour(t *testing.T) {
|
func TestDateNextHour(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Date.MustEastern())
|
||||||
next := Date.NextHour(start)
|
next := Date.NextHour(start)
|
||||||
assert.Equal(2015, next.Year())
|
assert.Equal(2015, next.Year())
|
||||||
assert.Equal(07, next.Month())
|
assert.Equal(07, next.Month())
|
||||||
|
@ -221,40 +136,3 @@ func TestDateNextDayOfWeek(t *testing.T) {
|
||||||
assert.Equal(time.UTC, nextSunday.Location())
|
assert.Equal(time.UTC, nextSunday.Location())
|
||||||
assert.Equal(time.UTC, nextMonday.Location())
|
assert.Equal(time.UTC, nextMonday.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDateIsNYSEHoliday(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
cursor := time.Date(2013, 01, 01, 0, 0, 0, 0, time.UTC)
|
|
||||||
end := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
var holidays int
|
|
||||||
for Date.Before(cursor, end) {
|
|
||||||
if Date.IsNYSEHoliday(cursor) {
|
|
||||||
holidays++
|
|
||||||
}
|
|
||||||
cursor = cursor.AddDate(0, 0, 1)
|
|
||||||
}
|
|
||||||
assert.Equal(holidays, 55)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateDiffDays(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
t1 := time.Date(2017, 02, 27, 12, 0, 0, 0, time.UTC)
|
|
||||||
t2 := time.Date(2017, 01, 10, 3, 0, 0, 0, time.UTC)
|
|
||||||
t3 := time.Date(2017, 02, 24, 16, 0, 0, 0, time.UTC)
|
|
||||||
|
|
||||||
assert.Equal(48, Date.DiffDays(t2, t1))
|
|
||||||
assert.Equal(2, Date.DiffDays(t3, t1)) // technically we should round down.
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateDiffHours(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
t1 := time.Date(2017, 02, 27, 12, 0, 0, 0, time.UTC)
|
|
||||||
t2 := time.Date(2017, 02, 24, 16, 0, 0, 0, time.UTC)
|
|
||||||
t3 := time.Date(2017, 02, 28, 12, 0, 0, 0, time.UTC)
|
|
||||||
|
|
||||||
assert.Equal(68, Date.DiffHours(t2, t1))
|
|
||||||
assert.Equal(24, Date.DiffHours(t1, t3))
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Eastern returns the eastern timezone.
|
|
||||||
func (d date) Eastern() *time.Location {
|
|
||||||
if _eastern == nil {
|
|
||||||
_easternLock.Lock()
|
|
||||||
defer _easternLock.Unlock()
|
|
||||||
if _eastern == nil {
|
|
||||||
_eastern, _ = time.LoadLocation("EST")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _eastern
|
|
||||||
}
|
|
74
util/time.go
74
util/time.go
|
@ -18,3 +18,77 @@ func (tu timeUtil) ToFloat64(t time.Time) float64 {
|
||||||
func (tu timeUtil) FromFloat64(tf float64) time.Time {
|
func (tu timeUtil) FromFloat64(tf float64) time.Time {
|
||||||
return time.Unix(0, int64(tf))
|
return time.Unix(0, int64(tf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tu timeUtil) DiffDays(t1, t2 time.Time) (days int) {
|
||||||
|
t1n := t1.Unix()
|
||||||
|
t2n := t2.Unix()
|
||||||
|
var diff int64
|
||||||
|
if t1n > t2n {
|
||||||
|
diff = t2n - t1n //yields seconds
|
||||||
|
} else {
|
||||||
|
diff = t1n - t2n //yields seconds
|
||||||
|
}
|
||||||
|
return int(diff / (_secondsPerDay))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tu timeUtil) DiffHours(t1, t2 time.Time) (hours int) {
|
||||||
|
t1n := t1.Unix()
|
||||||
|
t2n := t2.Unix()
|
||||||
|
var diff int64
|
||||||
|
if t1n > t2n {
|
||||||
|
diff = t1n - t2n
|
||||||
|
} else {
|
||||||
|
diff = t2n - t1n
|
||||||
|
}
|
||||||
|
return int(diff / (_secondsPerHour))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start returns the earliest (min) time in a list of times.
|
||||||
|
func (tu timeUtil) Start(times ...time.Time) time.Time {
|
||||||
|
if len(times) == 0 {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
start := times[0]
|
||||||
|
for _, t := range times[1:] {
|
||||||
|
if t.Before(start) {
|
||||||
|
start = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start returns the earliest (min) time in a list of times.
|
||||||
|
func (tu timeUtil) End(times ...time.Time) time.Time {
|
||||||
|
if len(times) == 0 {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := times[0]
|
||||||
|
for _, t := range times[1:] {
|
||||||
|
if t.After(end) {
|
||||||
|
end = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndEnd returns the start and end of a given set of time in one pass.
|
||||||
|
func (tu timeUtil) StartAndEnd(values ...time.Time) (start time.Time, end time.Time) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
start = values[0]
|
||||||
|
end = values[0]
|
||||||
|
|
||||||
|
for _, v := range values[1:] {
|
||||||
|
if end.Before(v) {
|
||||||
|
end = v
|
||||||
|
}
|
||||||
|
if start.After(v) {
|
||||||
|
start = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
30
util/time_test.go
Normal file
30
util/time_test.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blend/go-sdk/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimeDiffDays(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
t1 := time.Date(2017, 02, 27, 12, 0, 0, 0, time.UTC)
|
||||||
|
t2 := time.Date(2017, 01, 10, 3, 0, 0, 0, time.UTC)
|
||||||
|
t3 := time.Date(2017, 02, 24, 16, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
assert.Equal(48, Time.DiffDays(t2, t1))
|
||||||
|
assert.Equal(2, Time.DiffDays(t3, t1)) // technically we should round down.
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeDiffHours(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
t1 := time.Date(2017, 02, 27, 12, 0, 0, 0, time.UTC)
|
||||||
|
t2 := time.Date(2017, 02, 24, 16, 0, 0, 0, time.UTC)
|
||||||
|
t3 := time.Date(2017, 02, 28, 12, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
assert.Equal(68, Time.DiffHours(t2, t1))
|
||||||
|
assert.Equal(24, Time.DiffHours(t1, t3))
|
||||||
|
}
|
Loading…
Reference in a new issue