sequence tweaks, removing market hours anything

This commit is contained in:
Will Charczuk 2018-09-10 13:08:20 -07:00
parent 1a09989055
commit 0e849b11bb
17 changed files with 413 additions and 877 deletions

View file

@ -1,7 +1,6 @@
package util
import (
"sync"
"time"
)
@ -45,192 +44,88 @@ var (
Epoch = time.Unix(0, 0)
)
var (
_easternLock sync.Mutex
_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{}
)
// Date contains utility functions that operate on dates.
var Date date
type date struct{}
// IsNYSEHoliday returns if a date was/is on a nyse holiday day.
func (d date) IsNYSEHoliday(t time.Time) bool {
te := t.In(d.Eastern())
if te.Year() == 2013 {
if te.Month() == 1 {
return te.Day() == 1 || te.Day() == 21
} else if te.Month() == 2 {
return te.Day() == 18
} else if te.Month() == 3 {
return te.Day() == 29
} else if te.Month() == 5 {
return te.Day() == 27
} else if te.Month() == 7 {
return te.Day() == 4
} else if te.Month() == 9 {
return te.Day() == 2
} else if te.Month() == 11 {
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
func (d date) MustEastern() *time.Location {
if eastern, err := d.Eastern(); err != nil {
panic(err)
} else {
return eastern
}
}
// Eastern returns the eastern timezone.
func (d date) Eastern() (*time.Location, error) {
// Try POSIX
est, err := time.LoadLocation("America/New_York")
if err == nil {
// Try Windows
est, err = time.LoadLocation("EST")
if err == nil {
return nil, err
}
}
return false
return est, nil
}
// IsNYSEArcaHoliday returns that returns if a given time falls on a holiday.
func (d date) IsNYSEArcaHoliday(t time.Time) bool {
return d.IsNYSEHoliday(t)
func (d date) MustPacific() *time.Location {
if pst, err := d.Pacific(); err != nil {
panic(err)
} else {
return pst
}
}
// IsNASDAQHoliday returns if a date was a NASDAQ holiday day.
func (d date) IsNASDAQHoliday(t time.Time) bool {
return d.IsNYSEHoliday(t)
// Pacific returns the pacific timezone.
func (d date) Pacific() (*time.Location, error) {
// 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.
// It is meant to be used with the `OnDate` function.
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)
}
// 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 {
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.
func (d date) On(clock, cd time.Time) time.Time {
tzAdjusted := cd.In(clock.Location())
// OnDate returns the clock components of clock (hour,minute,second) on the date components of d.
func (d date) OnDate(clock, date time.Time) time.Time {
tzAdjusted := date.In(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.
func (d date) NoonOn(cd time.Time) time.Time {
// NoonOnDate is a shortcut for On(Time(12,0,0), cd) a.k.a. noon on a given date.
func (d date) NoonOnDate(cd time.Time) time.Time {
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.
func (d date) IsWeekDay(day time.Weekday) bool {
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()
}
// 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 (
_secondsPerHour = 60 * 60
_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.
func (d date) NextDay(ts time.Time) time.Time {
return ts.AddDate(0, 0, 1)