updates
This commit is contained in:
parent
255390c710
commit
472d04b3fa
14 changed files with 397 additions and 381 deletions
|
@ -3,6 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnnotationSeries is a series of labels on the chart.
|
// AnnotationSeries is a series of labels on the chart.
|
||||||
|
@ -55,10 +57,10 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
|
||||||
lx := canvasBox.Left + xrange.Translate(a.XValue)
|
lx := canvasBox.Left + xrange.Translate(a.XValue)
|
||||||
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
|
ly := canvasBox.Bottom - yrange.Translate(a.YValue)
|
||||||
ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
|
ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
|
||||||
box.Top = Math.MinInt(box.Top, ab.Top)
|
box.Top = util.Math.MinInt(box.Top, ab.Top)
|
||||||
box.Left = Math.MinInt(box.Left, ab.Left)
|
box.Left = util.Math.MinInt(box.Left, ab.Left)
|
||||||
box.Right = Math.MaxInt(box.Right, ab.Right)
|
box.Right = util.Math.MaxInt(box.Right, ab.Right)
|
||||||
box.Bottom = Math.MaxInt(box.Bottom, ab.Bottom)
|
box.Bottom = util.Math.MaxInt(box.Bottom, ab.Bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return box
|
return box
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BarChart is a chart that draws bars on a range.
|
// BarChart is a chart that draws bars on a range.
|
||||||
|
@ -368,7 +369,7 @@ func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range,
|
||||||
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
|
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
|
||||||
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
||||||
|
|
||||||
xaxisHeight = Math.MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
xaxisHeight = util.Math.MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,7 +436,7 @@ func (bc BarChart) styleDefaultsTitle() Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc BarChart) getTitleFontSize() float64 {
|
func (bc BarChart) getTitleFontSize() float64 {
|
||||||
effectiveDimension := Math.MinInt(bc.GetWidth(), bc.GetHeight())
|
effectiveDimension := util.Math.MinInt(bc.GetWidth(), bc.GetHeight())
|
||||||
if effectiveDimension >= 2048 {
|
if effectiveDimension >= 2048 {
|
||||||
return 48
|
return 48
|
||||||
} else if effectiveDimension >= 1024 {
|
} else if effectiveDimension >= 1024 {
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
// BollingerBandsSeries draws bollinger bands for an inner series.
|
// BollingerBandsSeries draws bollinger bands for an inner series.
|
||||||
// Bollinger bands are defined by two lines, one at SMA+k*stddev, one at SMA-k*stdev.
|
// Bollinger bands are defined by two lines, one at SMA+k*stddev, one at SMA-k*stdev.
|
||||||
|
@ -13,7 +17,7 @@ type BollingerBandsSeries struct {
|
||||||
K float64
|
K float64
|
||||||
InnerSeries ValuesProvider
|
InnerSeries ValuesProvider
|
||||||
|
|
||||||
valueBuffer *ValueBuffer
|
valueBuffer *util.ValueBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
// GetName returns the name of the time series.
|
||||||
|
@ -63,7 +67,7 @@ func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bbs.valueBuffer == nil || index == 0 {
|
if bbs.valueBuffer == nil || index == 0 {
|
||||||
bbs.valueBuffer = NewValueBufferWithCapacity(bbs.GetPeriod())
|
bbs.valueBuffer = util.NewValueBufferWithCapacity(bbs.GetPeriod())
|
||||||
}
|
}
|
||||||
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
if bbs.valueBuffer.Len() >= bbs.GetPeriod() {
|
||||||
bbs.valueBuffer.Dequeue()
|
bbs.valueBuffer.Dequeue()
|
||||||
|
@ -72,8 +76,8 @@ func (bbs *BollingerBandsSeries) GetBoundedValues(index int) (x, y1, y2 float64)
|
||||||
bbs.valueBuffer.Enqueue(py)
|
bbs.valueBuffer.Enqueue(py)
|
||||||
x = px
|
x = px
|
||||||
|
|
||||||
ay := Sequence{bbs.valueBuffer}.Average()
|
ay := util.Sequence{bbs.valueBuffer}.Average()
|
||||||
std := Sequence{bbs.valueBuffer}.StdDev()
|
std := util.Sequence{bbs.valueBuffer}.StdDev()
|
||||||
|
|
||||||
y1 = ay + (bbs.GetK() * std)
|
y1 = ay + (bbs.GetK() * std)
|
||||||
y2 = ay - (bbs.GetK() * std)
|
y2 = ay - (bbs.GetK() * std)
|
||||||
|
|
|
@ -3,6 +3,8 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MarketHoursRange is a special type of range that compresses a time range into just the
|
// MarketHoursRange is a special type of range that compresses a time range into just the
|
||||||
|
@ -14,7 +16,7 @@ type MarketHoursRange struct {
|
||||||
MarketOpen time.Time
|
MarketOpen time.Time
|
||||||
MarketClose time.Time
|
MarketClose time.Time
|
||||||
|
|
||||||
HolidayProvider HolidayProvider
|
HolidayProvider util.HolidayProvider
|
||||||
|
|
||||||
ValueFormatter ValueFormatter
|
ValueFormatter ValueFormatter
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
141
sequence/time.go
Normal file
141
sequence/time.go
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Time is a utility singleton with helper functions for time sequence generation.
|
||||||
|
var Time timeSequence
|
||||||
|
|
||||||
|
type timeSequence struct{}
|
||||||
|
|
||||||
|
// Days generates a sequence of timestamps by day, from -days to today.
|
||||||
|
func (ts timeSequence) Days(days int) []time.Time {
|
||||||
|
var values []time.Time
|
||||||
|
for day := days; day >= 0; day-- {
|
||||||
|
values = append(values, time.Now().AddDate(0, 0, -day))
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
times := make([]time.Time, totalHours)
|
||||||
|
|
||||||
|
last := start
|
||||||
|
for i := 0; i < totalHours; i++ {
|
||||||
|
times[i] = last
|
||||||
|
last = last.Add(time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
start := util.Date.Start(xdata)
|
||||||
|
end := util.Date.End(xdata)
|
||||||
|
|
||||||
|
totalHours := util.Math.AbsInt(util.Date.DiffHours(start, end))
|
||||||
|
|
||||||
|
finalTimes := ts.Hours(start, totalHours+1)
|
||||||
|
finalValues := make([]float64, totalHours+1)
|
||||||
|
|
||||||
|
var hoursFromStart int
|
||||||
|
for i, xd := range xdata {
|
||||||
|
hoursFromStart = util.Date.DiffHours(start, xd)
|
||||||
|
finalValues[hoursFromStart] = ydata[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalTimes, finalValues
|
||||||
|
}
|
232
sequence/value_buffer.go
Normal file
232
sequence/value_buffer.go
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
util "github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
valueBufferMinimumGrow = 4
|
||||||
|
valueBufferShrinkThreshold = 32
|
||||||
|
valueBufferGrowFactor = 200
|
||||||
|
valueBufferDefaultCapacity = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyArray = make([]float64, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewBuffer creates a new value buffer with an optional set of values.
|
||||||
|
func NewBuffer(values ...float64) *Buffer {
|
||||||
|
var tail int
|
||||||
|
array := make([]float64, util.Math.MaxInt(len(values), valueBufferDefaultCapacity))
|
||||||
|
if len(values) > 0 {
|
||||||
|
copy(array, values)
|
||||||
|
tail = len(values)
|
||||||
|
}
|
||||||
|
return &Buffer{
|
||||||
|
array: array,
|
||||||
|
head: 0,
|
||||||
|
tail: tail,
|
||||||
|
size: len(values),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
|
||||||
|
func NewBufferWithCapacity(capacity int) *Buffer {
|
||||||
|
return &Buffer{
|
||||||
|
array: make([]float64, capacity),
|
||||||
|
head: 0,
|
||||||
|
tail: 0,
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer is a fifo datastructure that is backed by a pre-allocated array.
|
||||||
|
// Instead of allocating a whole new node object for each element, array elements are re-used (which saves GC churn).
|
||||||
|
// Enqueue can be O(n), Dequeue is generally O(1).
|
||||||
|
// Buffer implements `sequence.Provider`
|
||||||
|
type Buffer struct {
|
||||||
|
array []float64
|
||||||
|
head int
|
||||||
|
tail int
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the ValueBuffer (as it is currently populated).
|
||||||
|
// Actual memory footprint may be different.
|
||||||
|
func (b *Buffer) Len() int {
|
||||||
|
return b.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue implements sequence provider.
|
||||||
|
func (b *Buffer) GetValue(index int) float64 {
|
||||||
|
effectiveIndex := (b.head + index) % len(b.array)
|
||||||
|
return b.array[effectiveIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity returns the total size of the ValueBuffer, including empty elements.
|
||||||
|
func (b *Buffer) Capacity() int {
|
||||||
|
return len(b.array)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCapacity sets the capacity of the ValueBuffer.
|
||||||
|
func (b *Buffer) SetCapacity(capacity int) {
|
||||||
|
newArray := make([]float64, capacity)
|
||||||
|
if b.size > 0 {
|
||||||
|
if b.head < b.tail {
|
||||||
|
arrayCopy(b.array, b.head, newArray, 0, b.size)
|
||||||
|
} else {
|
||||||
|
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
|
||||||
|
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.array = newArray
|
||||||
|
b.head = 0
|
||||||
|
if b.size == capacity {
|
||||||
|
b.tail = 0
|
||||||
|
} else {
|
||||||
|
b.tail = b.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all objects from the ValueBuffer.
|
||||||
|
func (b *Buffer) Clear() {
|
||||||
|
if b.head < b.tail {
|
||||||
|
arrayClear(b.array, b.head, b.size)
|
||||||
|
} else {
|
||||||
|
arrayClear(b.array, b.head, len(b.array)-b.head)
|
||||||
|
arrayClear(b.array, 0, b.tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.head = 0
|
||||||
|
b.tail = 0
|
||||||
|
b.size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue adds an element to the "back" of the ValueBuffer.
|
||||||
|
func (b *Buffer) Enqueue(value float64) {
|
||||||
|
if b.size == len(b.array) {
|
||||||
|
newCapacity := int(len(b.array) * int(valueBufferGrowFactor/100))
|
||||||
|
if newCapacity < (len(b.array) + valueBufferMinimumGrow) {
|
||||||
|
newCapacity = len(b.array) + valueBufferMinimumGrow
|
||||||
|
}
|
||||||
|
b.SetCapacity(newCapacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.array[b.tail] = value
|
||||||
|
b.tail = (b.tail + 1) % len(b.array)
|
||||||
|
b.size++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dequeue removes the first element from the RingBuffer.
|
||||||
|
func (b *Buffer) Dequeue() float64 {
|
||||||
|
if b.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
removed := b.array[b.head]
|
||||||
|
b.head = (b.head + 1) % len(b.array)
|
||||||
|
b.size--
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns but does not remove the first element.
|
||||||
|
func (b *Buffer) Peek() float64 {
|
||||||
|
if b.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return b.array[b.head]
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekBack returns but does not remove the last element.
|
||||||
|
func (b *Buffer) PeekBack() float64 {
|
||||||
|
if b.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if b.tail == 0 {
|
||||||
|
return b.array[len(b.array)-1]
|
||||||
|
}
|
||||||
|
return b.array[b.tail-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimExcess resizes the buffer to better fit the contents.
|
||||||
|
func (b *Buffer) TrimExcess() {
|
||||||
|
threshold := float64(len(b.array)) * 0.9
|
||||||
|
if b.size < int(threshold) {
|
||||||
|
b.SetCapacity(b.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array returns the ring buffer, in order, as an array.
|
||||||
|
func (b *Buffer) Array() Array {
|
||||||
|
newArray := make([]float64, b.size)
|
||||||
|
|
||||||
|
if b.size == 0 {
|
||||||
|
return newArray
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.head < b.tail {
|
||||||
|
arrayCopy(b.array, b.head, newArray, 0, b.size)
|
||||||
|
} else {
|
||||||
|
arrayCopy(b.array, b.head, newArray, 0, len(b.array)-b.head)
|
||||||
|
arrayCopy(b.array, 0, newArray, len(b.array)-b.head, b.tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array(newArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each calls the consumer for each element in the buffer.
|
||||||
|
func (b *Buffer) Each(mapfn func(int, float64)) {
|
||||||
|
if b.size == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var index int
|
||||||
|
if b.head < b.tail {
|
||||||
|
for cursor := b.head; cursor < b.tail; cursor++ {
|
||||||
|
mapfn(index, b.array[cursor])
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for cursor := b.head; cursor < len(b.array); cursor++ {
|
||||||
|
mapfn(index, b.array[cursor])
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
for cursor := 0; cursor < b.tail; cursor++ {
|
||||||
|
mapfn(index, b.array[cursor])
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation for value buffers.
|
||||||
|
func (b *Buffer) String() string {
|
||||||
|
var values []string
|
||||||
|
for _, elem := range b.Array() {
|
||||||
|
values = append(values, fmt.Sprintf("%v", elem))
|
||||||
|
}
|
||||||
|
return strings.Join(values, " <= ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
// Util methods
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func arrayClear(source []float64, index, length int) {
|
||||||
|
for x := 0; x < length; x++ {
|
||||||
|
absoluteIndex := x + index
|
||||||
|
source[absoluteIndex] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func arrayCopy(source []float64, sourceIndex int, destination []float64, destinationIndex, length int) {
|
||||||
|
for x := 0; x < length; x++ {
|
||||||
|
from := sourceIndex + x
|
||||||
|
to := destinationIndex + x
|
||||||
|
|
||||||
|
destination[to] = source[from]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,137 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// TimeSeries is a utility singleton with helper functions for time series generation.
|
|
||||||
var TimeSeries timeSeries
|
|
||||||
|
|
||||||
type timeSeries struct{}
|
|
||||||
|
|
||||||
// Days generates a sequence of timestamps by day, from -days to today.
|
|
||||||
func (ts timeSeries) Days(days int) []time.Time {
|
|
||||||
var values []time.Time
|
|
||||||
for day := days; day >= 0; day-- {
|
|
||||||
values = append(values, time.Now().AddDate(0, 0, -day))
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSeries) MarketHours(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := Date.On(marketOpen, from)
|
|
||||||
toClose := Date.On(marketClose, to)
|
|
||||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
|
||||||
todayOpen := Date.On(marketOpen, cursor)
|
|
||||||
todayClose := Date.On(marketClose, cursor)
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && 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 = Date.NextMarketOpen(cursor, marketOpen, isHoliday)
|
|
||||||
} else {
|
|
||||||
cursor = Date.NextHour(cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSeries) MarketHourQuarters(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := Date.On(marketOpen, from)
|
|
||||||
toClose := Date.On(marketClose, to)
|
|
||||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
|
||||||
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
|
||||||
|
|
||||||
if isValidTradingDay {
|
|
||||||
todayOpen := Date.On(marketOpen, cursor)
|
|
||||||
todayNoon := Date.NoonOn(cursor)
|
|
||||||
today2pm := Date.On(Date.Time(14, 0, 0, 0, cursor.Location()), cursor)
|
|
||||||
todayClose := Date.On(marketClose, cursor)
|
|
||||||
times = append(times, todayOpen, todayNoon, today2pm, todayClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = Date.NextDay(cursor)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSeries) MarketDayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := Date.On(marketOpen, from)
|
|
||||||
toClose := Date.On(marketClose, to)
|
|
||||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
|
||||||
if isValidTradingDay {
|
|
||||||
todayClose := Date.On(marketClose, cursor)
|
|
||||||
times = append(times, todayClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = Date.NextDay(cursor)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSeries) MarketDayAlternateCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := Date.On(marketOpen, from)
|
|
||||||
toClose := Date.On(marketClose, to)
|
|
||||||
for cursor.Before(toClose) || cursor.Equal(toClose) {
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
|
||||||
if isValidTradingDay {
|
|
||||||
todayClose := Date.On(marketClose, cursor)
|
|
||||||
times = append(times, todayClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = cursor.AddDate(0, 0, 2)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSeries) MarketDayMondayCloses(from, to time.Time, marketOpen, marketClose time.Time, isHoliday HolidayProvider) []time.Time {
|
|
||||||
var times []time.Time
|
|
||||||
cursor := Date.On(marketClose, from)
|
|
||||||
toClose := Date.On(marketClose, to)
|
|
||||||
|
|
||||||
for cursor.Equal(toClose) || cursor.Before(toClose) {
|
|
||||||
isValidTradingDay := !isHoliday(cursor) && Date.IsWeekDay(cursor.Weekday())
|
|
||||||
if isValidTradingDay {
|
|
||||||
times = append(times, cursor)
|
|
||||||
}
|
|
||||||
cursor = Date.NextDayOfWeek(cursor, time.Monday)
|
|
||||||
}
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts timeSeries) Hours(start time.Time, totalHours int) []time.Time {
|
|
||||||
times := make([]time.Time, totalHours)
|
|
||||||
|
|
||||||
last := start
|
|
||||||
for i := 0; i < totalHours; i++ {
|
|
||||||
times[i] = last
|
|
||||||
last = last.Add(time.Hour)
|
|
||||||
}
|
|
||||||
|
|
||||||
return times
|
|
||||||
}
|
|
||||||
|
|
||||||
// HoursFilled adds zero values for the data bounded by the start and end of the xdata array.
|
|
||||||
func (ts timeSeries) HoursFilled(xdata []time.Time, ydata []float64) ([]time.Time, []float64) {
|
|
||||||
start := Date.Start(xdata)
|
|
||||||
end := Date.End(xdata)
|
|
||||||
|
|
||||||
totalHours := Math.AbsInt(Date.DiffHours(start, end))
|
|
||||||
|
|
||||||
finalTimes := ts.Hours(start, totalHours+1)
|
|
||||||
finalValues := make([]float64, totalHours+1)
|
|
||||||
|
|
||||||
var hoursFromStart int
|
|
||||||
for i, xd := range xdata {
|
|
||||||
hoursFromStart = Date.DiffHours(start, xd)
|
|
||||||
finalValues[hoursFromStart] = ydata[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalTimes, finalValues
|
|
||||||
}
|
|
|
@ -1,229 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
valueBufferMinimumGrow = 4
|
|
||||||
valueBufferShrinkThreshold = 32
|
|
||||||
valueBufferGrowFactor = 200
|
|
||||||
valueBufferDefaultCapacity = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
emptyArray = make([]float64, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewValueBuffer creates a new value buffer with an optional set of values.
|
|
||||||
func NewValueBuffer(values ...float64) *ValueBuffer {
|
|
||||||
var tail int
|
|
||||||
array := make([]float64, Math.MaxInt(len(values), valueBufferDefaultCapacity))
|
|
||||||
if len(values) > 0 {
|
|
||||||
copy(array, values)
|
|
||||||
tail = len(values)
|
|
||||||
}
|
|
||||||
return &ValueBuffer{
|
|
||||||
array: array,
|
|
||||||
head: 0,
|
|
||||||
tail: tail,
|
|
||||||
size: len(values),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewValueBufferWithCapacity creates a new ValueBuffer pre-allocated with the given capacity.
|
|
||||||
func NewValueBufferWithCapacity(capacity int) *ValueBuffer {
|
|
||||||
return &ValueBuffer{
|
|
||||||
array: make([]float64, capacity),
|
|
||||||
head: 0,
|
|
||||||
tail: 0,
|
|
||||||
size: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValueBuffer is a fifo buffer that is backed by a pre-allocated array, instead of allocating
|
|
||||||
// a whole new node object for each element (which saves GC churn).
|
|
||||||
// Enqueue can be O(n), Dequeue can be O(1).
|
|
||||||
type ValueBuffer struct {
|
|
||||||
array []float64
|
|
||||||
head int
|
|
||||||
tail int
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the length of the ValueBuffer (as it is currently populated).
|
|
||||||
// Actual memory footprint may be different.
|
|
||||||
func (vb *ValueBuffer) Len() int {
|
|
||||||
return vb.size
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue implements sequence provider.
|
|
||||||
func (vb *ValueBuffer) GetValue(index int) float64 {
|
|
||||||
effectiveIndex := (vb.head + index) % len(vb.array)
|
|
||||||
return vb.array[effectiveIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capacity returns the total size of the ValueBuffer, including empty elements.
|
|
||||||
func (vb *ValueBuffer) Capacity() int {
|
|
||||||
return len(vb.array)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCapacity sets the capacity of the ValueBuffer.
|
|
||||||
func (vb *ValueBuffer) SetCapacity(capacity int) {
|
|
||||||
newArray := make([]float64, capacity)
|
|
||||||
if vb.size > 0 {
|
|
||||||
if vb.head < vb.tail {
|
|
||||||
arrayCopy(vb.array, vb.head, newArray, 0, vb.size)
|
|
||||||
} else {
|
|
||||||
arrayCopy(vb.array, vb.head, newArray, 0, len(vb.array)-vb.head)
|
|
||||||
arrayCopy(vb.array, 0, newArray, len(vb.array)-vb.head, vb.tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vb.array = newArray
|
|
||||||
vb.head = 0
|
|
||||||
if vb.size == capacity {
|
|
||||||
vb.tail = 0
|
|
||||||
} else {
|
|
||||||
vb.tail = vb.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all objects from the ValueBuffer.
|
|
||||||
func (vb *ValueBuffer) Clear() {
|
|
||||||
if vb.head < vb.tail {
|
|
||||||
arrayClear(vb.array, vb.head, vb.size)
|
|
||||||
} else {
|
|
||||||
arrayClear(vb.array, vb.head, len(vb.array)-vb.head)
|
|
||||||
arrayClear(vb.array, 0, vb.tail)
|
|
||||||
}
|
|
||||||
|
|
||||||
vb.head = 0
|
|
||||||
vb.tail = 0
|
|
||||||
vb.size = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enqueue adds an element to the "back" of the ValueBuffer.
|
|
||||||
func (vb *ValueBuffer) Enqueue(value float64) {
|
|
||||||
if vb.size == len(vb.array) {
|
|
||||||
newCapacity := int(len(vb.array) * int(valueBufferGrowFactor/100))
|
|
||||||
if newCapacity < (len(vb.array) + valueBufferMinimumGrow) {
|
|
||||||
newCapacity = len(vb.array) + valueBufferMinimumGrow
|
|
||||||
}
|
|
||||||
vb.SetCapacity(newCapacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
vb.array[vb.tail] = value
|
|
||||||
vb.tail = (vb.tail + 1) % len(vb.array)
|
|
||||||
vb.size++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dequeue removes the first element from the RingBuffer.
|
|
||||||
func (vb *ValueBuffer) Dequeue() float64 {
|
|
||||||
if vb.size == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
removed := vb.array[vb.head]
|
|
||||||
vb.head = (vb.head + 1) % len(vb.array)
|
|
||||||
vb.size--
|
|
||||||
return removed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peek returns but does not remove the first element.
|
|
||||||
func (vb *ValueBuffer) Peek() float64 {
|
|
||||||
if vb.size == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return vb.array[vb.head]
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeekBack returns but does not remove the last element.
|
|
||||||
func (vb *ValueBuffer) PeekBack() float64 {
|
|
||||||
if vb.size == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if vb.tail == 0 {
|
|
||||||
return vb.array[len(vb.array)-1]
|
|
||||||
}
|
|
||||||
return vb.array[vb.tail-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrimExcess resizes the buffer to better fit the contents.
|
|
||||||
func (vb *ValueBuffer) TrimExcess() {
|
|
||||||
threshold := float64(len(vb.array)) * 0.9
|
|
||||||
if vb.size < int(threshold) {
|
|
||||||
vb.SetCapacity(vb.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array returns the ring buffer, in order, as an array.
|
|
||||||
func (vb *ValueBuffer) Array() Array {
|
|
||||||
newArray := make([]float64, vb.size)
|
|
||||||
|
|
||||||
if vb.size == 0 {
|
|
||||||
return newArray
|
|
||||||
}
|
|
||||||
|
|
||||||
if vb.head < vb.tail {
|
|
||||||
arrayCopy(vb.array, vb.head, newArray, 0, vb.size)
|
|
||||||
} else {
|
|
||||||
arrayCopy(vb.array, vb.head, newArray, 0, len(vb.array)-vb.head)
|
|
||||||
arrayCopy(vb.array, 0, newArray, len(vb.array)-vb.head, vb.tail)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array(newArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each calls the consumer for each element in the buffer.
|
|
||||||
func (vb *ValueBuffer) Each(mapfn func(int, float64)) {
|
|
||||||
if vb.size == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var index int
|
|
||||||
if vb.head < vb.tail {
|
|
||||||
for cursor := vb.head; cursor < vb.tail; cursor++ {
|
|
||||||
mapfn(index, vb.array[cursor])
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for cursor := vb.head; cursor < len(vb.array); cursor++ {
|
|
||||||
mapfn(index, vb.array[cursor])
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
for cursor := 0; cursor < vb.tail; cursor++ {
|
|
||||||
mapfn(index, vb.array[cursor])
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation for value buffers.
|
|
||||||
func (vb *ValueBuffer) String() string {
|
|
||||||
var values []string
|
|
||||||
for _, elem := range vb.Array() {
|
|
||||||
values = append(values, fmt.Sprintf("%v", elem))
|
|
||||||
}
|
|
||||||
return strings.Join(values, " <= ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------
|
|
||||||
// Util methods
|
|
||||||
// --------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
func arrayClear(source []float64, index, length int) {
|
|
||||||
for x := 0; x < length; x++ {
|
|
||||||
absoluteIndex := x + index
|
|
||||||
source[absoluteIndex] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func arrayCopy(source []float64, sourceIndex int, destination []float64, destinationIndex, length int) {
|
|
||||||
for x := 0; x < length; x++ {
|
|
||||||
from := sourceIndex + x
|
|
||||||
to := destinationIndex + x
|
|
||||||
|
|
||||||
destination[to] = source[from]
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue