diff --git a/pkg/bbgo/marketdatastore.go b/pkg/bbgo/marketdatastore.go index e506da426..03f17d1f2 100644 --- a/pkg/bbgo/marketdatastore.go +++ b/pkg/bbgo/marketdatastore.go @@ -11,9 +11,9 @@ type MarketDataStore struct { Symbol string // KLineWindows stores all loaded klines per interval - KLineWindows map[types.Interval]types.KLineWindow `json:"-"` + KLineWindows map[types.Interval]*types.KLineWindow `json:"-"` - kLineWindowUpdateCallbacks []func(interval types.Interval, kline types.KLineWindow) + kLineWindowUpdateCallbacks []func(interval types.Interval, klines types.KLineWindow) } func NewMarketDataStore(symbol string) *MarketDataStore { @@ -21,16 +21,16 @@ func NewMarketDataStore(symbol string) *MarketDataStore { Symbol: symbol, // KLineWindows stores all loaded klines per interval - KLineWindows: make(map[types.Interval]types.KLineWindow, len(types.SupportedIntervals)), // 12 interval, 1m,5m,15m,30m,1h,2h,4h,6h,12h,1d,3d,1w + KLineWindows: make(map[types.Interval]*types.KLineWindow, len(types.SupportedIntervals)), // 12 interval, 1m,5m,15m,30m,1h,2h,4h,6h,12h,1d,3d,1w } } -func (store *MarketDataStore) SetKLineWindows(windows map[types.Interval]types.KLineWindow) { +func (store *MarketDataStore) SetKLineWindows(windows map[types.Interval]*types.KLineWindow) { store.KLineWindows = windows } // KLinesOfInterval returns the kline window of the given interval -func (store *MarketDataStore) KLinesOfInterval(interval types.Interval) (kLines types.KLineWindow, ok bool) { +func (store *MarketDataStore) KLinesOfInterval(interval types.Interval) (kLines *types.KLineWindow, ok bool) { kLines, ok = store.KLineWindows[interval] return kLines, ok } @@ -50,14 +50,15 @@ func (store *MarketDataStore) handleKLineClosed(kline types.KLine) { func (store *MarketDataStore) AddKLine(kline types.KLine) { window, ok := store.KLineWindows[kline.Interval] if !ok { - window = make(types.KLineWindow, 0, 1000) + var tmp = make(types.KLineWindow, 0, 1000) + store.KLineWindows[kline.Interval] = &tmp + window = &tmp } window.Add(kline) - if len(window) > MaxNumOfKLines { - window = window[MaxNumOfKLinesTruncate-1:] + if len(*window) > MaxNumOfKLines { + *window = (*window)[MaxNumOfKLinesTruncate-1:] } - store.KLineWindows[kline.Interval] = window - store.EmitKLineWindowUpdate(kline.Interval, window) + store.EmitKLineWindowUpdate(kline.Interval, *window) } diff --git a/pkg/bbgo/marketdatastore_callbacks.go b/pkg/bbgo/marketdatastore_callbacks.go index f81bd3b48..4acaccb10 100644 --- a/pkg/bbgo/marketdatastore_callbacks.go +++ b/pkg/bbgo/marketdatastore_callbacks.go @@ -6,12 +6,12 @@ import ( "github.com/c9s/bbgo/pkg/types" ) -func (store *MarketDataStore) OnKLineWindowUpdate(cb func(interval types.Interval, kline types.KLineWindow)) { +func (store *MarketDataStore) OnKLineWindowUpdate(cb func(interval types.Interval, klines types.KLineWindow)) { store.kLineWindowUpdateCallbacks = append(store.kLineWindowUpdateCallbacks, cb) } -func (store *MarketDataStore) EmitKLineWindowUpdate(interval types.Interval, kline types.KLineWindow) { +func (store *MarketDataStore) EmitKLineWindowUpdate(interval types.Interval, klines types.KLineWindow) { for _, cb := range store.kLineWindowUpdateCallbacks { - cb(interval, kline) + cb(interval, klines) } } diff --git a/pkg/indicator/boll.go b/pkg/indicator/boll.go index 491b00968..894e1ac90 100644 --- a/pkg/indicator/boll.go +++ b/pkg/indicator/boll.go @@ -42,19 +42,19 @@ type BOLL struct { type BandType int func (inc *BOLL) GetUpBand() types.Series { - return inc.UpBand + return &inc.UpBand } func (inc *BOLL) GetDownBand() types.Series { - return inc.DownBand + return &inc.DownBand } func (inc *BOLL) GetSMA() types.Series { - return inc.SMA + return &inc.SMA } func (inc *BOLL) GetStdDev() types.Series { - return inc.StdDev + return &inc.StdDev } func (inc *BOLL) LastUpBand() float64 { diff --git a/pkg/indicator/sma.go b/pkg/indicator/sma.go index a4fbca562..d726cd209 100644 --- a/pkg/indicator/sma.go +++ b/pkg/indicator/sma.go @@ -45,6 +45,16 @@ func (inc *SMA) Length() int { var _ types.Series = &SMA{} +func (inc *SMA) Update(value float64) { + length := len(inc.Values) + if length == 0 { + inc.Values = append(inc.Values, value) + return + } + newVal := (inc.Values[length-1]*float64(inc.Window-1) + value) / float64(inc.Window) + inc.Values = append(inc.Values, newVal) +} + func (inc *SMA) calculateAndUpdate(kLines []types.KLine) { if len(kLines) < inc.Window { return diff --git a/pkg/indicator/stoch.go b/pkg/indicator/stoch.go index 0a909f977..9e43e3919 100644 --- a/pkg/indicator/stoch.go +++ b/pkg/indicator/stoch.go @@ -84,9 +84,9 @@ func (inc *STOCH) Bind(updater KLineWindowUpdater) { } func (inc *STOCH) GetD() types.Series { - return inc.D + return &inc.D } func (inc *STOCH) GetK() types.Series { - return inc.K + return &inc.K } diff --git a/pkg/types/float_slice.go b/pkg/types/float_slice.go index 7648a35ad..4972f3ff0 100644 --- a/pkg/types/float_slice.go +++ b/pkg/types/float_slice.go @@ -118,22 +118,28 @@ func (s Float64Slice) Dot(other Float64Slice) float64 { return s.ElementwiseProduct(other).Sum() } -func (a Float64Slice) Last() float64 { - if len(a) > 0 { - return a[len(a)-1] +func (a *Float64Slice) Last() float64 { + length := len(*a) + if length > 0 { + return (*a)[length-1] } return 0.0 } -func (a Float64Slice) Index(i int) float64 { - if len(a)-i < 0 || i < 0 { +func (a *Float64Slice) Index(i int) float64 { + length := len(*a) + if length-i < 0 || i < 0 { return 0.0 } - return a[len(a)-i-1] + return (*a)[length-i-1] } -func (a Float64Slice) Length() int { - return len(a) +func (a *Float64Slice) Length() int { + return len(*a) } -var _ Series = Float64Slice([]float64{}) +func (a Float64Slice) Addr() *Float64Slice { + return &a +} + +var _ Series = Float64Slice([]float64{}).Addr() diff --git a/pkg/types/indicator.go b/pkg/types/indicator.go index e25a02fc5..1185ee126 100644 --- a/pkg/types/indicator.go +++ b/pkg/types/indicator.go @@ -1,7 +1,9 @@ package types import ( + "fmt" "math" + "reflect" "gonum.org/v1/gonum/stat" ) @@ -83,6 +85,25 @@ func Abs(a Series) Series { var _ Series = &AbsResult{} +func Predict(a Series, lookback int, offset ...int) float64 { + if a.Length() < lookback { + lookback = a.Length() + } + x := make([]float64, lookback, lookback) + y := make([]float64, lookback, lookback) + var weights []float64 + for i := 0; i < lookback; i++ { + x[i] = float64(i) + y[i] = a.Index(i) + } + alpha, beta := stat.LinearRegression(x, y, weights, false) + o := -1.0 + if len(offset) > 0 { + o = -float64(offset[0]) + } + return alpha + beta*o +} + // This will make prediction using Linear Regression to get the next cross point // Return (offset from latest, crossed value, could cross) // offset from latest should always be positive @@ -96,14 +117,14 @@ func NextCross(a Series, b Series, lookback int) (int, float64, bool) { if b.Length() < lookback { lookback = b.Length() } - x := make([]float64, 0, lookback) - y1 := make([]float64, 0, lookback) - y2 := make([]float64, 0, lookback) + x := make([]float64, lookback, lookback) + y1 := make([]float64, lookback, lookback) + y2 := make([]float64, lookback, lookback) var weights []float64 for i := 0; i < lookback; i++ { - x = append(x, float64(i)) - y1 = append(y1, a.Index(i)) - y2 = append(y2, b.Index(i)) + x[i] = float64(i) + y1[i] = a.Index(i) + y2[i] = b.Index(i) } alpha1, beta1 := stat.LinearRegression(x, y1, weights, false) alpha2, beta2 := stat.LinearRegression(x, y2, weights, false) @@ -233,12 +254,18 @@ func Add(a interface{}, b interface{}) Series { aa = NumberSeries(a.(float64)) case Series: aa = a.(Series) + default: + panic("input should be either *Series or float64") + } switch b.(type) { case float64: bb = NumberSeries(b.(float64)) case Series: bb = b.(Series) + default: + panic("input should be either *Series or float64") + } return &AddSeriesResult{aa, bb} } @@ -269,21 +296,8 @@ type MinusSeriesResult struct { // Minus two series, result[i] = a[i] - b[i] func Minus(a interface{}, b interface{}) Series { - var aa Series - var bb Series - - switch a.(type) { - case float64: - aa = NumberSeries(a.(float64)) - case Series: - aa = a.(Series) - } - switch b.(type) { - case float64: - bb = NumberSeries(b.(float64)) - case Series: - bb = b.(Series) - } + aa := switchIface(a) + bb := switchIface(b) return &MinusSeriesResult{aa, bb} } @@ -306,26 +320,34 @@ func (a *MinusSeriesResult) Length() int { var _ Series = &MinusSeriesResult{} -// Divid two series, result[i] = a[i] / b[i] -func Div(a interface{}, b interface{}) Series { - var aa Series - var bb Series - - switch a.(type) { - case float64: - aa = NumberSeries(a.(float64)) - case Series: - aa = a.(Series) - } +func switchIface(b interface{}) Series { switch b.(type) { case float64: - bb = NumberSeries(b.(float64)) - if 0 == bb.Last() { - panic("Divid by zero exception") - } + return NumberSeries(b.(float64)) + case int32: + return NumberSeries(float64(b.(int32))) + case int64: + return NumberSeries(float64(b.(int64))) + case float32: + return NumberSeries(float64(b.(float32))) + case int: + return NumberSeries(float64(b.(int))) case Series: - bb = b.(Series) + return b.(Series) + default: + fmt.Println(reflect.TypeOf(b)) + panic("input should be either *Series or float64") + } +} + +// Divid two series, result[i] = a[i] / b[i] +func Div(a interface{}, b interface{}) Series { + aa := switchIface(a) + if 0 == b { + panic("Divid by zero exception") + } + bb := switchIface(b) return &DivSeriesResult{aa, bb} } @@ -364,12 +386,17 @@ func Mul(a interface{}, b interface{}) Series { aa = NumberSeries(a.(float64)) case Series: aa = a.(Series) + default: + panic("input should be either Series or float64") } switch b.(type) { case float64: bb = NumberSeries(b.(float64)) case Series: bb = b.(Series) + default: + panic("input should be either Series or float64") + } return &MulSeriesResult{aa, bb} @@ -443,3 +470,43 @@ func ToReverseArray(a Series, limit ...int) (result Float64Slice) { } return } + +type ChangeResult struct { + a Series + offset int +} + +func (c *ChangeResult) Last() float64 { + if c.offset >= c.a.Length() { + return 0 + } + return c.a.Last() - c.a.Index(c.offset) +} + +func (c *ChangeResult) Index(i int) float64 { + if i+c.offset >= c.a.Length() { + return 0 + } + return c.a.Index(i) - c.a.Index(i+c.offset) +} + +func (c *ChangeResult) Length() int { + length := c.a.Length() + if length >= c.offset { + return length - c.offset + } + return 0 +} + +// Difference between current value and previous, a - a[offset] +// offset: if not given, offset is 1. +func Change(a Series, offset ...int) Series { + o := 1 + if len(offset) == 0 { + o = offset[0] + } + + return &ChangeResult{a, o} +} + +// TODO: ta.linreg diff --git a/pkg/types/indicator_test.go b/pkg/types/indicator_test.go index ab9916d48..8919c7866 100644 --- a/pkg/types/indicator_test.go +++ b/pkg/types/indicator_test.go @@ -14,7 +14,7 @@ func TestFloat(t *testing.T) { func TestNextCross(t *testing.T) { var a Series = NumberSeries(1.2) - var b Series = Float64Slice{100., 80., 60.} + var b Series = &Float64Slice{100., 80., 60.} // index 2 1 0 // predicted 40 20 0 // offset 1 2 3 @@ -24,3 +24,12 @@ func TestNextCross(t *testing.T) { assert.Equal(t, value, 1.2) assert.Equal(t, index, 3) // 2.94, ceil } + +func TestFloat64Slice(t *testing.T) { + var a = Float64Slice{1.0, 2.0, 3.0} + var b = Float64Slice{1.0, 2.0, 3.0} + var c Series = Minus(&a, &b) + a = append(a, 4.0) + b = append(b, 3.0) + assert.Equal(t, c.Last(), 1.) +} diff --git a/pkg/types/kline.go b/pkg/types/kline.go index 0d109bd57..675cd027c 100644 --- a/pkg/types/kline.go +++ b/pkg/types/kline.go @@ -516,76 +516,88 @@ const ( kCloseValue kHighValue kLowValue + kVolumeValue ) -func (k KLineWindow) High() Series { +func (k *KLineWindow) High() Series { return &KLineSeries{ lines: k, kv: kHighValue, } } -func (k KLineWindow) Low() Series { +func (k *KLineWindow) Low() Series { return &KLineSeries{ lines: k, kv: kLowValue, } } -func (k KLineWindow) Open() Series { +func (k *KLineWindow) Open() Series { return &KLineSeries{ lines: k, kv: kOpenValue, } } -func (k KLineWindow) Close() Series { +func (k *KLineWindow) Close() Series { return &KLineSeries{ lines: k, kv: kCloseValue, } } +func (k *KLineWindow) Volume() Series { + return &KLineSeries{ + lines: k, + kv: kVolumeValue, + } +} + type KLineSeries struct { - lines []KLine + lines *KLineWindow kv KValueType } func (k *KLineSeries) Last() float64 { - length := len(k.lines) + length := len(*k.lines) switch k.kv { case kOpenValue: - return k.lines[length-1].GetOpen().Float64() + return (*k.lines)[length-1].GetOpen().Float64() case kCloseValue: - return k.lines[length-1].GetClose().Float64() + return (*k.lines)[length-1].GetClose().Float64() case kLowValue: - return k.lines[length-1].GetLow().Float64() + return (*k.lines)[length-1].GetLow().Float64() case kHighValue: - return k.lines[length-1].GetHigh().Float64() + return (*k.lines)[length-1].GetHigh().Float64() + case kVolumeValue: + return (*k.lines)[length-1].Volume.Float64() } return 0 } func (k *KLineSeries) Index(i int) float64 { - length := len(k.lines) + length := len(*k.lines) if length == 0 || length-i-1 < 0 { return 0 } switch k.kv { case kOpenValue: - return k.lines[length-i-1].GetOpen().Float64() + return (*k.lines)[length-i-1].GetOpen().Float64() case kCloseValue: - return k.lines[length-i-1].GetClose().Float64() + return (*k.lines)[length-i-1].GetClose().Float64() case kLowValue: - return k.lines[length-i-1].GetLow().Float64() + return (*k.lines)[length-i-1].GetLow().Float64() case kHighValue: - return k.lines[length-i-1].GetHigh().Float64() + return (*k.lines)[length-i-1].GetHigh().Float64() + case kVolumeValue: + return (*k.lines)[length-i-1].Volume.Float64() } return 0 } func (k *KLineSeries) Length() int { - return len(k.lines) + return len(*k.lines) } var _ Series = &KLineSeries{}