This commit is contained in:
Will Charczuk 2017-04-29 16:18:09 -07:00
parent 255390c710
commit 472d04b3fa
14 changed files with 397 additions and 381 deletions

View file

@ -1,61 +0,0 @@
package util
import (
"math/rand"
"time"
)
var (
// Generate contains some sequence generation utilities.
// These utilities can be useful for generating test data.
Generate = &generate{
rnd: rand.New(rand.NewSource(time.Now().Unix())),
}
)
type generate struct {
rnd *rand.Rand
}
// Values produces an array of floats from [start,end] by optional steps.
func (g generate) Values(start, end float64, steps ...float64) Sequence {
var values []float64
step := 1.0
if len(steps) > 0 {
step = steps[0]
}
if start < end {
for x := start; x <= end; x += step {
values = append(values, x)
}
} else {
for x := start; x >= end; x = x - step {
values = append(values, x)
}
}
return Sequence{Array(values)}
}
// Random generates a fixed length sequence of random values between (0, scale).
func (g generate) RandomValues(samples int, scale float64) Sequence {
values := make([]float64, samples)
for x := 0; x < samples; x++ {
values[x] = g.rnd.Float64() * scale
}
return Sequence{Array(values)}
}
// Random generates a fixed length sequence of random values with a given average, above and below that average by (-scale, scale)
func (g generate) RandomValuesWithAverage(samples int, average, scale float64) Sequence {
values := make([]float64, samples)
for x := 0; x < samples; x++ {
jitter := scale - (g.rnd.Float64() * (2 * scale))
values[x] = average + jitter
}
return Sequence{Array(values)}
}

View file

@ -1,92 +0,0 @@
package util
import (
"testing"
"time"
assert "github.com/blendlabs/go-assert"
)
func TestSequenceFloat64(t *testing.T) {
assert := assert.New(t)
asc := Generate.Float64(1.0, 10.0)
assert.Len(asc, 10)
desc := Generate.Float64(10.0, 1.0)
assert.Len(desc, 10)
}
func TestSequenceMarketHours(t *testing.T) {
assert := assert.New(t)
today := time.Date(2016, 07, 01, 12, 0, 0, 0, Date.Eastern())
mh := Generate.MarketHours(today, today, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday)
assert.Len(mh, 8)
assert.Equal(Date.Eastern(), mh[0].Location())
}
func TestSequenceMarketQuarters(t *testing.T) {
assert := assert.New(t)
today := time.Date(2016, 07, 01, 12, 0, 0, 0, Date.Eastern())
mh := Generate.MarketHourQuarters(today, today, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday)
assert.Len(mh, 4)
assert.Equal(9, mh[0].Hour())
assert.Equal(30, mh[0].Minute())
assert.Equal(Date.Eastern(), mh[0].Location())
assert.Equal(12, mh[1].Hour())
assert.Equal(00, mh[1].Minute())
assert.Equal(Date.Eastern(), mh[1].Location())
assert.Equal(14, mh[2].Hour())
assert.Equal(00, mh[2].Minute())
assert.Equal(Date.Eastern(), mh[2].Location())
}
func TestSequenceHours(t *testing.T) {
assert := assert.New(t)
today := time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC)
seq := Generate.Hours(today, 24)
end := Date.End(seq)
assert.Len(seq, 24)
assert.Equal(2016, end.Year())
assert.Equal(07, int(end.Month()))
assert.Equal(02, end.Day())
assert.Equal(11, end.Hour())
}
func TestSequenceHoursFill(t *testing.T) {
assert := assert.New(t)
xdata := []time.Time{
time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC),
time.Date(2016, 07, 01, 13, 0, 0, 0, time.UTC),
time.Date(2016, 07, 01, 14, 0, 0, 0, time.UTC),
time.Date(2016, 07, 02, 4, 0, 0, 0, time.UTC),
time.Date(2016, 07, 02, 5, 0, 0, 0, time.UTC),
time.Date(2016, 07, 03, 12, 0, 0, 0, time.UTC),
time.Date(2016, 07, 03, 14, 0, 0, 0, time.UTC),
}
ydata := []float64{
1.1,
1.2,
1.4,
0.8,
2.1,
0.4,
0.6,
}
filledTimes, filledValues := Generate.HoursFilled(xdata, ydata)
assert.Len(filledTimes, Date.DiffHours(Date.Start(xdata), Date.End(xdata))+1)
assert.Equal(len(filledValues), len(filledTimes))
assert.NotZero(filledValues[0])
assert.NotZero(filledValues[len(filledValues)-1])
assert.NotZero(filledValues[16])
}

View file

@ -1,82 +0,0 @@
package util
import "math"
// SequenceProvider is a provider for values for a sequence.
type SequenceProvider interface {
Len() int
GetValue(int) float64
}
// Sequence is a utility wrapper for sequence providers.
type Sequence struct {
SequenceProvider
}
// Each applies the `mapfn` to all values in the value provider.
func (s Sequence) Each(mapfn func(int, float64)) {
for i := 0; i < s.Len(); i++ {
mapfn(i, s.GetValue(i))
}
}
// Map applies the `mapfn` to all values in the value provider,
// returning a new sequence.
func (s Sequence) Map(mapfn func(i int, v float64) float64) Sequence {
output := make([]float64, s.Len())
for i := 0; i < s.Len(); i++ {
mapfn(i, s.GetValue(i))
}
return Sequence{Array(output)}
}
// Average returns the float average of the values in the buffer.
func (s Sequence) Average() float64 {
if s.Len() == 0 {
return 0
}
var accum float64
for i := 0; i < s.Len(); i++ {
accum += s.GetValue(i)
}
return accum / float64(s.Len())
}
// Variance computes the variance of the buffer.
func (s Sequence) Variance() float64 {
if s.Len() == 0 {
return 0
}
m := s.Average()
var variance, v float64
for i := 0; i < s.Len(); i++ {
v = s.GetValue(i)
variance += (v - m) * (v - m)
}
return variance / float64(s.Len())
}
// StdDev returns the standard deviation.
func (s Sequence) StdDev() float64 {
if s.Len() == 0 {
return 0
}
return math.Pow(s.Variance(), 0.5)
}
// Array is a wrapper for an array of floats that implements `ValuesProvider`.
type Array []float64
// Len returns the value provider length.
func (a Array) Len() int {
return len(a)
}
// GetValue returns the value at a given index.
func (a Array) GetValue(index int) float64 {
return a[index]
}

View file

@ -1 +0,0 @@
package util

View file

@ -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
}

View file

@ -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]
}
}

View file

@ -1,182 +0,0 @@
package util
import (
"testing"
"github.com/blendlabs/go-assert"
)
func TestValueBuffer(t *testing.T) {
assert := assert.New(t)
buffer := NewValueBuffer()
buffer.Enqueue(1)
assert.Equal(1, buffer.Len())
assert.Equal(1, buffer.Peek())
assert.Equal(1, buffer.PeekBack())
buffer.Enqueue(2)
assert.Equal(2, buffer.Len())
assert.Equal(1, buffer.Peek())
assert.Equal(2, buffer.PeekBack())
buffer.Enqueue(3)
assert.Equal(3, buffer.Len())
assert.Equal(1, buffer.Peek())
assert.Equal(3, buffer.PeekBack())
buffer.Enqueue(4)
assert.Equal(4, buffer.Len())
assert.Equal(1, buffer.Peek())
assert.Equal(4, buffer.PeekBack())
buffer.Enqueue(5)
assert.Equal(5, buffer.Len())
assert.Equal(1, buffer.Peek())
assert.Equal(5, buffer.PeekBack())
buffer.Enqueue(6)
assert.Equal(6, buffer.Len())
assert.Equal(1, buffer.Peek())
assert.Equal(6, buffer.PeekBack())
buffer.Enqueue(7)
assert.Equal(7, buffer.Len())
assert.Equal(1, buffer.Peek())
assert.Equal(7, buffer.PeekBack())
buffer.Enqueue(8)
assert.Equal(8, buffer.Len())
assert.Equal(1, buffer.Peek())
assert.Equal(8, buffer.PeekBack())
value := buffer.Dequeue()
assert.Equal(1, value)
assert.Equal(7, buffer.Len())
assert.Equal(2, buffer.Peek())
assert.Equal(8, buffer.PeekBack())
value = buffer.Dequeue()
assert.Equal(2, value)
assert.Equal(6, buffer.Len())
assert.Equal(3, buffer.Peek())
assert.Equal(8, buffer.PeekBack())
value = buffer.Dequeue()
assert.Equal(3, value)
assert.Equal(5, buffer.Len())
assert.Equal(4, buffer.Peek())
assert.Equal(8, buffer.PeekBack())
value = buffer.Dequeue()
assert.Equal(4, value)
assert.Equal(4, buffer.Len())
assert.Equal(5, buffer.Peek())
assert.Equal(8, buffer.PeekBack())
value = buffer.Dequeue()
assert.Equal(5, value)
assert.Equal(3, buffer.Len())
assert.Equal(6, buffer.Peek())
assert.Equal(8, buffer.PeekBack())
value = buffer.Dequeue()
assert.Equal(6, value)
assert.Equal(2, buffer.Len())
assert.Equal(7, buffer.Peek())
assert.Equal(8, buffer.PeekBack())
value = buffer.Dequeue()
assert.Equal(7, value)
assert.Equal(1, buffer.Len())
assert.Equal(8, buffer.Peek())
assert.Equal(8, buffer.PeekBack())
value = buffer.Dequeue()
assert.Equal(8, value)
assert.Equal(0, buffer.Len())
assert.Zero(buffer.Peek())
assert.Zero(buffer.PeekBack())
}
func TestRingBufferClear(t *testing.T) {
assert := assert.New(t)
buffer := NewValueBuffer()
buffer.Enqueue(1)
buffer.Enqueue(1)
buffer.Enqueue(1)
buffer.Enqueue(1)
buffer.Enqueue(1)
buffer.Enqueue(1)
buffer.Enqueue(1)
buffer.Enqueue(1)
assert.Equal(8, buffer.Len())
buffer.Clear()
assert.Equal(0, buffer.Len())
assert.Zero(buffer.Peek())
assert.Zero(buffer.PeekBack())
}
func TestRingBufferAsSlice(t *testing.T) {
assert := assert.New(t)
buffer := NewValueBuffer()
buffer.Enqueue(1)
buffer.Enqueue(2)
buffer.Enqueue(3)
buffer.Enqueue(4)
buffer.Enqueue(5)
contents := buffer.Array()
assert.Len(contents, 5)
assert.Equal(1, contents[0])
assert.Equal(2, contents[1])
assert.Equal(3, contents[2])
assert.Equal(4, contents[3])
assert.Equal(5, contents[4])
}
func TestRingBufferEach(t *testing.T) {
assert := assert.New(t)
buffer := NewValueBuffer()
for x := 1; x < 17; x++ {
buffer.Enqueue(float64(x))
}
called := 0
buffer.Each(func(_ int, v float64) {
if v == float64(called+1) {
called++
}
})
assert.Equal(16, called)
}
func TestNewValueBuffer(t *testing.T) {
assert := assert.New(t)
empty := NewValueBuffer()
assert.NotNil(empty)
assert.Zero(empty.Len())
assert.Equal(valueBufferDefaultCapacity, empty.Capacity())
assert.Zero(empty.Peek())
assert.Zero(empty.PeekBack())
}
func TestNewValueBufferWithValues(t *testing.T) {
assert := assert.New(t)
values := NewValueBuffer(1, 2, 3, 4)
assert.NotNil(values)
assert.Equal(4, values.Len())
assert.Equal(valueBufferDefaultCapacity, values.Capacity())
assert.Equal(1, values.Peek())
assert.Equal(4, values.PeekBack())
}