candle series, candle series tests.
This commit is contained in:
parent
e39acdfb76
commit
7d1401898a
3 changed files with 97 additions and 10 deletions
|
@ -3,8 +3,13 @@ package chart
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CandleValue is a day's data for a candlestick plot.
|
||||||
type CandleValue struct {
|
type CandleValue struct {
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
High float64
|
High float64
|
||||||
|
@ -13,6 +18,11 @@ type CandleValue struct {
|
||||||
Close float64
|
Close float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsZero returns if the value is zero or not.
|
||||||
|
func (cv CandleValue) IsZero() bool {
|
||||||
|
return cv.Timestamp.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
// CandlestickSeries is a special type of series that takes a norma value provider
|
// CandlestickSeries is a special type of series that takes a norma value provider
|
||||||
// and maps it to day value stats (high, low, open, close).
|
// and maps it to day value stats (high, low, open, close).
|
||||||
type CandlestickSeries struct {
|
type CandlestickSeries struct {
|
||||||
|
@ -46,20 +56,52 @@ func (cs CandlestickSeries) CandleValues() []CandleValue {
|
||||||
// compute the low, or the min
|
// compute the low, or the min
|
||||||
|
|
||||||
totalValues := cs.InnerSeries.Len()
|
totalValues := cs.InnerSeries.Len()
|
||||||
|
if totalValues == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var value CandleValue
|
||||||
var values []CandleValue
|
var values []CandleValue
|
||||||
|
var lastYear, lastMonth, lastDay int
|
||||||
|
var year, month, day int
|
||||||
|
|
||||||
var day int
|
var tv float64
|
||||||
for i := 0; i < totalValues; i++ {
|
var t time.Time
|
||||||
if day == 0 {
|
var lv, v float64
|
||||||
// extract day value from time value
|
|
||||||
|
tv, v = cs.InnerSeries.GetValues(0)
|
||||||
|
t = util.Time.FromFloat64(tv)
|
||||||
|
year, month, day = t.Year(), int(t.Month()), t.Day()
|
||||||
|
value.Timestamp = cs.newTimestamp(year, month, day)
|
||||||
|
value.Open, value.Low, value.High = v, v, v
|
||||||
|
|
||||||
|
for i := 1; i < totalValues; i++ {
|
||||||
|
tv, v = cs.InnerSeries.GetValues(i)
|
||||||
|
t = util.Time.FromFloat64(tv)
|
||||||
|
year, month, day = t.Year(), int(t.Month()), t.Day()
|
||||||
|
|
||||||
|
// if we've transitioned to a new day or we're on the last value
|
||||||
|
if lastYear != year || lastMonth != month || lastDay != day || i == (totalValues-1) {
|
||||||
|
value.Close = lv
|
||||||
|
values = append(values, value)
|
||||||
|
|
||||||
|
value = CandleValue{
|
||||||
|
Timestamp: cs.newTimestamp(year, month, day),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if
|
|
||||||
|
value.Low = math.Min(value.Low, v)
|
||||||
|
value.High = math.Max(value.Low, v)
|
||||||
|
lv = v
|
||||||
}
|
}
|
||||||
|
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cs CandlestickSeries) newTimestamp(year, month, day int) time.Time {
|
||||||
|
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, util.Date.Eastern())
|
||||||
|
}
|
||||||
|
|
||||||
// Render implements Series.Render.
|
// Render implements Series.Render.
|
||||||
func (cs CandlestickSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
func (cs CandlestickSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
//style := cs.Style.InheritFrom(defaults)
|
//style := cs.Style.InheritFrom(defaults)
|
||||||
|
|
45
candlestick_series_test.go
Normal file
45
candlestick_series_test.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
"github.com/wcharczuk/go-chart/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateDummyStockData() (times []time.Time, prices []float64) {
|
||||||
|
start := util.Date.On(time.Date(2017, 05, 15, 6, 30, 0, 0, util.Date.Eastern()), util.NYSEOpen())
|
||||||
|
var cursor time.Time
|
||||||
|
for day := 0; day < 60; day++ {
|
||||||
|
cursor = start.AddDate(0, 0, day)
|
||||||
|
for hour := 0; hour < 7; hour++ {
|
||||||
|
for minute := 0; minute < 60; minute++ {
|
||||||
|
times = append(times, cursor)
|
||||||
|
prices = append(prices, rand.Float64()*256)
|
||||||
|
|
||||||
|
cursor = cursor.Add(time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = cursor.Add(time.Hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCandlestickSeriesCandleValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
xdata, ydata := generateDummyStockData()
|
||||||
|
|
||||||
|
candleSeries := CandlestickSeries{
|
||||||
|
InnerSeries: TimeSeries{
|
||||||
|
XValues: xdata,
|
||||||
|
YValues: ydata,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
values := candleSeries.CandleValues()
|
||||||
|
assert.NotEmpty(values)
|
||||||
|
}
|
|
@ -115,31 +115,31 @@ func (mhr MarketHoursRange) GetMarketClose() time.Time {
|
||||||
// GetTicks returns the ticks for the range.
|
// GetTicks returns the ticks for the range.
|
||||||
// This is to override the default continous ticks that would be generated 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 {
|
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())
|
times := seq.TimeUtil.MarketHours(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth := mhr.measureTimes(r, defaults, vf, times)
|
timesWidth := mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
times = seq.Time.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
times = seq.TimeUtil.MarketHourQuarters(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
times = seq.Time.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
times = seq.TimeUtil.MarketDayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
times = seq.Time.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
times = seq.TimeUtil.MarketDayAlternateCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
times = seq.Time.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
times = seq.TimeUtil.MarketDayMondayCloses(mhr.Min, mhr.Max, mhr.GetMarketOpen(), mhr.GetMarketClose(), mhr.GetHolidayProvider())
|
||||||
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
timesWidth = mhr.measureTimes(r, defaults, vf, times)
|
||||||
if timesWidth <= mhr.Domain {
|
if timesWidth <= mhr.Domain {
|
||||||
return mhr.makeTicks(vf, times)
|
return mhr.makeTicks(vf, times)
|
||||||
|
|
Loading…
Reference in a new issue