2017-05-13 22:00:35 +02:00
|
|
|
package chart
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
2017-05-14 22:34:46 +02:00
|
|
|
|
|
|
|
"math"
|
|
|
|
|
|
|
|
"github.com/wcharczuk/go-chart/util"
|
2017-05-13 22:00:35 +02:00
|
|
|
)
|
|
|
|
|
2017-05-14 22:34:46 +02:00
|
|
|
// CandleValue is a day's data for a candlestick plot.
|
2017-05-13 22:00:35 +02:00
|
|
|
type CandleValue struct {
|
|
|
|
Timestamp time.Time
|
|
|
|
High float64
|
|
|
|
Low float64
|
|
|
|
Open float64
|
|
|
|
Close float64
|
|
|
|
}
|
|
|
|
|
2017-05-15 01:33:48 +02:00
|
|
|
// String returns a string value for the candle value.
|
|
|
|
func (cv CandleValue) String() string {
|
|
|
|
return fmt.Sprintf("candle %s high: %.2f low: %.2f open: %.2f close: %.2f", cv.Timestamp.Format("2006-01-02"), cv.High, cv.Low, cv.Open, cv.Close)
|
|
|
|
}
|
|
|
|
|
2017-05-14 22:34:46 +02:00
|
|
|
// IsZero returns if the value is zero or not.
|
|
|
|
func (cv CandleValue) IsZero() bool {
|
|
|
|
return cv.Timestamp.IsZero()
|
|
|
|
}
|
|
|
|
|
2017-05-13 22:00:35 +02:00
|
|
|
// CandlestickSeries is a special type of series that takes a norma value provider
|
|
|
|
// and maps it to day value stats (high, low, open, close).
|
|
|
|
type CandlestickSeries struct {
|
2017-05-15 01:33:48 +02:00
|
|
|
Name string
|
|
|
|
Style Style
|
|
|
|
YAxis YAxisType
|
|
|
|
|
2017-05-17 02:50:17 +02:00
|
|
|
// CandleValues will be used in place of creating them from the `InnerSeries`.
|
|
|
|
CandleValues []CandleValue
|
|
|
|
|
|
|
|
// InnerSeries is used if the `CandleValues` are not set.
|
|
|
|
InnerSeries ValuesProvider
|
2017-05-13 22:00:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetName implements Series.GetName.
|
2017-05-17 02:50:17 +02:00
|
|
|
func (cs *CandlestickSeries) GetName() string {
|
2017-05-13 22:00:35 +02:00
|
|
|
return cs.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetStyle implements Series.GetStyle.
|
2017-05-17 02:50:17 +02:00
|
|
|
func (cs *CandlestickSeries) GetStyle() Style {
|
2017-05-13 22:00:35 +02:00
|
|
|
return cs.Style
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetYAxis returns which yaxis the series is mapped to.
|
2017-05-17 02:50:17 +02:00
|
|
|
func (cs *CandlestickSeries) GetYAxis() YAxisType {
|
2017-05-13 22:00:35 +02:00
|
|
|
return cs.YAxis
|
|
|
|
}
|
|
|
|
|
2017-05-15 01:33:48 +02:00
|
|
|
// Len returns the length of the series.
|
2017-05-17 02:50:17 +02:00
|
|
|
func (cs *CandlestickSeries) Len() int {
|
|
|
|
return len(cs.GetCandleValues())
|
2017-05-15 01:33:48 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 02:50:17 +02:00
|
|
|
// GetBoundedValues returns the bounded values at a given index.
|
|
|
|
func (cs *CandlestickSeries) GetBoundedValues(index int) (x, y0, y1 float64) {
|
|
|
|
value := cs.GetCandleValues()[index]
|
|
|
|
return util.Time.ToFloat64(value.Timestamp), value.Low, value.High
|
2017-05-15 03:58:10 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 02:50:17 +02:00
|
|
|
// GetCandleValues returns the candle values.
|
|
|
|
func (cs CandlestickSeries) GetCandleValues() []CandleValue {
|
|
|
|
if cs.CandleValues == nil {
|
|
|
|
cs.CandleValues = cs.GenerateCandleValues()
|
|
|
|
}
|
|
|
|
return cs.CandleValues
|
2017-05-15 01:33:48 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 02:50:17 +02:00
|
|
|
// GenerateCandleValues returns the candlestick values for each day represented by the inner series.
|
|
|
|
func (cs CandlestickSeries) GenerateCandleValues() []CandleValue {
|
|
|
|
totalValues := cs.InnerSeries.Len()
|
2017-05-14 22:34:46 +02:00
|
|
|
if totalValues == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2017-05-13 22:00:35 +02:00
|
|
|
|
|
|
|
var values []CandleValue
|
2017-05-14 22:34:46 +02:00
|
|
|
var lastYear, lastMonth, lastDay int
|
|
|
|
var year, month, day int
|
|
|
|
|
|
|
|
var t time.Time
|
2017-05-17 02:50:17 +02:00
|
|
|
var tv, lv, v float64
|
2017-05-13 22:00:35 +02:00
|
|
|
|
2017-05-17 02:50:17 +02:00
|
|
|
tv, v = cs.InnerSeries.GetValues(0)
|
|
|
|
t = util.Time.FromFloat64(tv)
|
2017-05-14 22:34:46 +02:00
|
|
|
year, month, day = t.Year(), int(t.Month()), t.Day()
|
2017-05-15 01:33:48 +02:00
|
|
|
|
|
|
|
lastYear, lastMonth, lastDay = year, month, day
|
|
|
|
|
|
|
|
value := CandleValue{
|
|
|
|
Timestamp: cs.newTimestamp(year, month, day),
|
|
|
|
Open: v,
|
|
|
|
Low: v,
|
|
|
|
High: v,
|
|
|
|
}
|
|
|
|
lv = v
|
2017-05-14 22:34:46 +02:00
|
|
|
|
|
|
|
for i := 1; i < totalValues; i++ {
|
2017-05-17 02:50:17 +02:00
|
|
|
tv, v = cs.InnerSeries.GetValues(0)
|
|
|
|
t = util.Time.FromFloat64(tv)
|
2017-05-14 22:34:46 +02:00
|
|
|
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),
|
2017-05-15 01:33:48 +02:00
|
|
|
Open: v,
|
|
|
|
High: v,
|
|
|
|
Low: v,
|
2017-05-14 22:34:46 +02:00
|
|
|
}
|
|
|
|
|
2017-05-15 01:33:48 +02:00
|
|
|
lastYear = year
|
|
|
|
lastMonth = month
|
|
|
|
lastDay = day
|
|
|
|
} else {
|
|
|
|
value.Low = math.Min(value.Low, v)
|
|
|
|
value.High = math.Max(value.High, v)
|
|
|
|
}
|
2017-05-14 22:34:46 +02:00
|
|
|
lv = v
|
2017-05-13 22:00:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return values
|
|
|
|
}
|
|
|
|
|
2017-05-14 22:34:46 +02:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2017-05-13 22:00:35 +02:00
|
|
|
// Render implements Series.Render.
|
|
|
|
func (cs CandlestickSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
2017-05-15 01:33:48 +02:00
|
|
|
style := cs.Style.InheritFrom(defaults)
|
|
|
|
Draw.CandlestickSeries(r, canvasBox, xrange, yrange, style, cs)
|
2017-05-13 22:00:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate validates the series.
|
|
|
|
func (cs CandlestickSeries) Validate() error {
|
2017-05-17 02:50:17 +02:00
|
|
|
if cs.CandleValues == nil && cs.InnerSeries == nil {
|
|
|
|
return fmt.Errorf("candlestick series requires either `CandleValues` or `InnerSeries` to be set")
|
2017-05-13 22:00:35 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|