go-chart/market_hours_range.go

196 lines
5.7 KiB
Go
Raw Normal View History

2016-07-22 07:09:09 +02:00
package chart
import (
"fmt"
"time"
"github.com/wcharczuk/go-chart/seq"
"github.com/wcharczuk/go-chart/util"
2016-07-22 07:09:09 +02:00
)
2016-07-23 20:50:30 +02:00
// MarketHoursRange is a special type of range that compresses a time range into just the
2016-07-22 07:09:09 +02:00
// market (i.e. NYSE operating hours and days) range.
2016-07-23 20:50:30 +02:00
type MarketHoursRange struct {
Min time.Time
Max time.Time
MarketOpen time.Time
MarketClose time.Time
HolidayProvider util.HolidayProvider
2016-07-23 20:50:30 +02:00
2016-07-27 09:20:43 +02:00
ValueFormatter ValueFormatter
2017-01-10 22:50:17 +01:00
Descending bool
Domain int
}
// IsDescending returns if the range is descending.
func (mhr MarketHoursRange) IsDescending() bool {
return mhr.Descending
2016-07-22 07:09:09 +02:00
}
2016-08-01 09:50:32 +02:00
// GetTimezone returns the timezone for the market hours range.
func (mhr MarketHoursRange) GetTimezone() *time.Location {
return mhr.GetMarketOpen().Location()
}
2016-07-22 07:22:22 +02:00
// IsZero returns if the range is setup or not.
2016-07-23 20:50:30 +02:00
func (mhr MarketHoursRange) IsZero() bool {
2016-07-22 07:22:22 +02:00
return mhr.Min.IsZero() && mhr.Max.IsZero()
}
2016-07-22 07:09:09 +02:00
// GetMin returns the min value.
2016-07-23 20:50:30 +02:00
func (mhr MarketHoursRange) GetMin() float64 {
return util.Time.ToFloat64(mhr.Min)
2016-07-22 07:09:09 +02:00
}
// GetMax returns the max value.
2016-07-23 20:50:30 +02:00
func (mhr MarketHoursRange) GetMax() float64 {
return util.Time.ToFloat64(mhr.GetEffectiveMax())
2016-07-27 17:21:05 +02:00
}
// 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)
2016-07-27 17:21:05 +02:00
if maxClose.After(mhr.Max) {
return maxClose
}
return mhr.Max
2016-07-22 07:09:09 +02:00
}
// SetMin sets the min value.
2016-07-23 20:50:30 +02:00
func (mhr *MarketHoursRange) SetMin(min float64) {
mhr.Min = util.Time.FromFloat64(min)
2016-08-01 09:50:32 +02:00
mhr.Min = mhr.Min.In(mhr.GetTimezone())
2016-07-22 07:09:09 +02:00
}
// SetMax sets the max value.
2016-07-23 20:50:30 +02:00
func (mhr *MarketHoursRange) SetMax(max float64) {
mhr.Max = util.Time.FromFloat64(max)
2016-08-01 09:50:32 +02:00
mhr.Max = mhr.Max.In(mhr.GetTimezone())
2016-07-22 07:09:09 +02:00
}
// GetDelta gets the delta.
2016-07-23 20:50:30 +02:00
func (mhr MarketHoursRange) GetDelta() float64 {
2016-07-27 17:21:05 +02:00
min := mhr.GetMin()
max := mhr.GetMax()
2016-07-22 07:09:09 +02:00
return max - min
}
// GetDomain gets the domain.
2016-07-23 20:50:30 +02:00
func (mhr MarketHoursRange) GetDomain() int {
2016-07-22 07:09:09 +02:00
return mhr.Domain
}
// SetDomain sets the domain.
2016-07-23 20:50:30 +02:00
func (mhr *MarketHoursRange) SetDomain(domain int) {
2016-07-22 07:09:09 +02:00
mhr.Domain = domain
}
2016-07-24 00:35:49 +02:00
// GetHolidayProvider coalesces a userprovided holiday provider and the date.DefaultHolidayProvider.
func (mhr MarketHoursRange) GetHolidayProvider() util.HolidayProvider {
2016-07-24 00:35:49 +02:00
if mhr.HolidayProvider == nil {
return func(_ time.Time) bool { return false }
2016-07-24 00:35:49 +02:00
}
return mhr.HolidayProvider
}
2016-08-01 01:54:09 +02:00
// GetMarketOpen returns the market open time.
func (mhr MarketHoursRange) GetMarketOpen() time.Time {
if mhr.MarketOpen.IsZero() {
return util.NYSEOpen()
2016-08-01 01:54:09 +02:00
}
return mhr.MarketOpen
}
// GetMarketClose returns the market close time.
func (mhr MarketHoursRange) GetMarketClose() time.Time {
if mhr.MarketClose.IsZero() {
return util.NYSEClose()
2016-08-01 01:54:09 +02:00
}
return mhr.MarketClose
}
2016-07-24 00:35:49 +02:00
// GetTicks returns the ticks for the range.
// This is to override the default continous ticks that would be generated for the range.
2016-08-01 01:54:09 +02:00
func (mhr *MarketHoursRange) GetTicks(r Renderer, defaults Style, vf ValueFormatter) []Tick {
2017-05-14 22:34:46 +02:00
times := seq.TimeUtil.MarketHours(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
2016-08-01 01:54:09 +02:00
timesWidth := mhr.measureTimes(r, defaults, vf, times)
if timesWidth <= mhr.Domain {
return mhr.makeTicks(vf, times)
}
2016-08-01 09:50:32 +02:00
2017-05-14 22:34:46 +02:00
times = seq.TimeUtil.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
2016-08-01 01:54:09 +02:00
timesWidth = mhr.measureTimes(r, defaults, vf, times)
if timesWidth <= mhr.Domain {
return mhr.makeTicks(vf, times)
2016-07-24 00:35:49 +02:00
}
2016-08-01 09:50:32 +02:00
2017-05-14 22:34:46 +02:00
times = seq.TimeUtil.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
2016-08-01 09:50:32 +02:00
timesWidth = mhr.measureTimes(r, defaults, vf, times)
if timesWidth <= mhr.Domain {
return mhr.makeTicks(vf, times)
}
2017-05-14 22:34:46 +02:00
times = seq.TimeUtil.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
2016-08-01 09:50:32 +02:00
timesWidth = mhr.measureTimes(r, defaults, vf, times)
if timesWidth <= mhr.Domain {
return mhr.makeTicks(vf, times)
}
2017-05-14 22:34:46 +02:00
times = seq.TimeUtil.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
2016-08-01 09:50:32 +02:00
timesWidth = mhr.measureTimes(r, defaults, vf, times)
if timesWidth <= mhr.Domain {
return mhr.makeTicks(vf, times)
}
return GenerateContinuousTicks(r, mhr, false, defaults, vf)
2016-08-01 01:54:09 +02:00
}
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)
2016-07-24 00:35:49 +02:00
2016-08-01 01:54:09 +02:00
labelBox := r.MeasureText(timeLabel)
total += labelBox.Width()
if index > 0 {
total += DefaultMinimumTickHorizontalSpacing
}
2016-07-24 22:48:10 +02:00
}
2016-08-01 01:54:09 +02:00
return total
}
2016-07-31 06:34:41 +02:00
2016-08-01 01:54:09 +02:00
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),
2016-08-01 01:54:09 +02:00
Label: vf(t),
}
}
2016-07-24 00:35:49 +02:00
return ticks
}
2016-07-23 20:50:30 +02:00
func (mhr MarketHoursRange) String() string {
2016-08-01 09:50:32 +02:00
return fmt.Sprintf("MarketHoursRange [%s, %s] => %d", mhr.Min.Format(time.RFC3339), mhr.Max.Format(time.RFC3339), mhr.Domain)
2016-07-22 07:09:09 +02:00
}
// Translate maps a given value into the ContinuousRange space.
2016-07-23 20:50:30 +02:00
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)
2016-07-27 09:34:10 +02:00
translated := int((float64(valueDelta) / float64(totalSeconds)) * float64(mhr.Domain))
2017-01-10 22:52:18 +01:00
2017-01-10 22:52:34 +01:00
if mhr.IsDescending() {
return mhr.Domain - translated
2017-01-10 22:52:18 +01:00
}
2016-07-23 07:43:27 +02:00
return translated
2016-07-22 07:09:09 +02:00
}