Skip to content

Commit 58d2384

Browse files
authored
Merge pull request #821 from c9s/refactor/indicator-api
refactor: refactor indicator api (canonicalize CalculateAndUpdate, PushK methods)
2 parents ce3f7a3 + 975d0d6 commit 58d2384

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+532
-372
lines changed

doc/development/indicator.md

+26-6
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,43 @@ try to create new indicators in `pkg/indicator/` folder, and add compilation hin
6969
// go:generate callbackgen -type StructName
7070
type StructName struct {
7171
...
72-
UpdateCallbacks []func(value float64)
72+
updateCallbacks []func(value float64)
7373
}
7474

7575
```
7676
And implement required interface methods:
7777
```go
78+
79+
func (inc *StructName) Update(value float64) {
80+
// indicator calculation here...
81+
// push value...
82+
}
83+
84+
func (inc *StructName) PushK(k types.KLine) {
85+
inc.Update(k.Close.Float64())
86+
}
87+
7888
// custom function
79-
func (inc *StructName) calculateAndUpdate(kLines []types.KLine) {
80-
// calculation...
81-
// assign the result to calculatedValue
82-
inc.EmitUpdate(calculatedValue) // produce data, broadcast to the subscribers
89+
func (inc *StructName) CalculateAndUpdate(kLines []types.KLine) {
90+
if len(inc.Values) == 0 {
91+
// preload or initialization
92+
for _, k := range allKLines {
93+
inc.PushK(k)
94+
}
95+
96+
inc.EmitUpdate(inc.Last())
97+
} else {
98+
// update new value only
99+
k := allKLines[len(allKLines)-1]
100+
inc.PushK(k)
101+
inc.EmitUpdate(calculatedValue) // produce data, broadcast to the subscribers
102+
}
83103
}
84104

85105
// custom function
86106
func (inc *StructName) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
87107
// filter on interval
88-
inc.calculateAndUpdate(window)
108+
inc.CalculateAndUpdate(window)
89109
}
90110

91111
// required

pkg/bbgo/session.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type StandardIndicatorSet struct {
4343
ewma map[types.IntervalWindow]*indicator.EWMA
4444
boll map[types.IntervalWindowBandWidth]*indicator.BOLL
4545
stoch map[types.IntervalWindow]*indicator.STOCH
46-
volatility map[types.IntervalWindow]*indicator.VOLATILITY
46+
volatility map[types.IntervalWindow]*indicator.Volatility
4747

4848
store *MarketDataStore
4949
}
@@ -55,7 +55,7 @@ func NewStandardIndicatorSet(symbol string, store *MarketDataStore) *StandardInd
5555
ewma: make(map[types.IntervalWindow]*indicator.EWMA),
5656
boll: make(map[types.IntervalWindowBandWidth]*indicator.BOLL),
5757
stoch: make(map[types.IntervalWindow]*indicator.STOCH),
58-
volatility: make(map[types.IntervalWindow]*indicator.VOLATILITY),
58+
volatility: make(map[types.IntervalWindow]*indicator.Volatility),
5959
store: store,
6060
}
6161

@@ -146,10 +146,10 @@ func (set *StandardIndicatorSet) STOCH(iw types.IntervalWindow) *indicator.STOCH
146146
}
147147

148148
// VOLATILITY returns the volatility(stddev) indicator of the given interval and the window size.
149-
func (set *StandardIndicatorSet) VOLATILITY(iw types.IntervalWindow) *indicator.VOLATILITY {
149+
func (set *StandardIndicatorSet) VOLATILITY(iw types.IntervalWindow) *indicator.Volatility {
150150
inc, ok := set.volatility[iw]
151151
if !ok {
152-
inc = &indicator.VOLATILITY{IntervalWindow: iw}
152+
inc = &indicator.Volatility{IntervalWindow: iw}
153153
inc.Bind(set.store)
154154
set.volatility[iw] = inc
155155
}

pkg/indicator/ad.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func (inc *AD) Length() int {
5959

6060
var _ types.SeriesExtend = &AD{}
6161

62-
func (inc *AD) calculateAndUpdate(kLines []types.KLine) {
62+
func (inc *AD) CalculateAndUpdate(kLines []types.KLine) {
6363
for _, k := range kLines {
6464
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
6565
continue
@@ -70,12 +70,13 @@ func (inc *AD) calculateAndUpdate(kLines []types.KLine) {
7070
inc.EmitUpdate(inc.Last())
7171
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
7272
}
73+
7374
func (inc *AD) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
7475
if inc.Interval != interval {
7576
return
7677
}
7778

78-
inc.calculateAndUpdate(window)
79+
inc.CalculateAndUpdate(window)
7980
}
8081

8182
func (inc *AD) Bind(updater KLineWindowUpdater) {

pkg/indicator/alma.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (inc *ALMA) Length() int {
7575

7676
var _ types.SeriesExtend = &ALMA{}
7777

78-
func (inc *ALMA) calculateAndUpdate(allKLines []types.KLine) {
78+
func (inc *ALMA) CalculateAndUpdate(allKLines []types.KLine) {
7979
if inc.input == nil {
8080
for _, k := range allKLines {
8181
inc.Update(k.Close.Float64())
@@ -91,7 +91,7 @@ func (inc *ALMA) handleKLineWindowUpdate(interval types.Interval, window types.K
9191
if inc.Interval != interval {
9292
return
9393
}
94-
inc.calculateAndUpdate(window)
94+
inc.CalculateAndUpdate(window)
9595
}
9696

9797
func (inc *ALMA) Bind(updater KLineWindowUpdater) {

pkg/indicator/alma_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func Test_ALMA(t *testing.T) {
5252
Offset: 0.9,
5353
Sigma: 6,
5454
}
55-
alma.calculateAndUpdate(tt.kLines)
55+
alma.CalculateAndUpdate(tt.kLines)
5656
assert.InDelta(t, tt.want, alma.Last(), Delta)
5757
assert.InDelta(t, tt.next, alma.Index(1), Delta)
5858
assert.Equal(t, tt.all, alma.Length())

pkg/indicator/atr.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type ATR struct {
2020
UpdateCallbacks []func(value float64)
2121
}
2222

23+
var _ types.SeriesExtend = &ATR{}
24+
2325
func (inc *ATR) Update(high, low, cloze float64) {
2426
if inc.Window <= 0 {
2527
panic("window must be greater than 0")
@@ -72,17 +74,20 @@ func (inc *ATR) Length() int {
7274
if inc.RMA == nil {
7375
return 0
7476
}
77+
7578
return inc.RMA.Length()
7679
}
7780

78-
var _ types.SeriesExtend = &ATR{}
81+
func (inc *ATR) PushK(k types.KLine) {
82+
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
83+
}
7984

8085
func (inc *ATR) CalculateAndUpdate(kLines []types.KLine) {
8186
for _, k := range kLines {
8287
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
8388
continue
8489
}
85-
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
90+
inc.PushK(k)
8691
}
8792

8893
inc.EmitUpdate(inc.Last())

pkg/indicator/atrp.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,17 @@ func (inc *ATRP) Length() int {
8787

8888
var _ types.SeriesExtend = &ATRP{}
8989

90+
func (inc *ATRP) PushK(k types.KLine) {
91+
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
92+
}
93+
9094
func (inc *ATRP) CalculateAndUpdate(kLines []types.KLine) {
9195
for _, k := range kLines {
9296
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
9397
continue
9498
}
95-
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
99+
100+
inc.PushK(k)
96101
}
97102

98103
inc.EmitUpdate(inc.Last())

pkg/indicator/boll.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (inc *BOLL) LastSMA() float64 {
8888
return 0.0
8989
}
9090

91-
func (inc *BOLL) Update(kLines []types.KLine) {
91+
func (inc *BOLL) CalculateAndUpdate(kLines []types.KLine) {
9292
if len(kLines) < inc.Window {
9393
return
9494
}
@@ -142,7 +142,7 @@ func (inc *BOLL) handleKLineWindowUpdate(interval types.Interval, window types.K
142142
return
143143
}
144144

145-
inc.Update(window)
145+
inc.CalculateAndUpdate(window)
146146
}
147147

148148
func (inc *BOLL) Bind(updater KLineWindowUpdater) {

pkg/indicator/boll_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func TestBOLL(t *testing.T) {
5959
for _, tt := range tests {
6060
t.Run(tt.name, func(t *testing.T) {
6161
boll := BOLL{IntervalWindow: types.IntervalWindow{Window: tt.window}, K: tt.k}
62-
boll.Update(tt.kLines)
62+
boll.CalculateAndUpdate(tt.kLines)
6363
assert.InDelta(t, tt.up, boll.LastUpBand(), Delta)
6464
assert.InDelta(t, tt.down, boll.LastDownBand(), Delta)
6565
})

pkg/indicator/cci.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package indicator
33
import (
44
"math"
55

6-
"github.com/c9s/bbgo/pkg/fixedpoint"
76
"github.com/c9s/bbgo/pkg/types"
87
)
98

@@ -79,17 +78,20 @@ func (inc *CCI) Length() int {
7978

8079
var _ types.SeriesExtend = &CCI{}
8180

82-
var three = fixedpoint.NewFromInt(3)
8381

84-
func (inc *CCI) calculateAndUpdate(allKLines []types.KLine) {
82+
func (inc *CCI) PushK(k types.KLine) {
83+
inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64())
84+
}
85+
86+
func (inc *CCI) CalculateAndUpdate(allKLines []types.KLine) {
8587
if inc.TypicalPrice.Length() == 0 {
8688
for _, k := range allKLines {
87-
inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64())
89+
inc.PushK(k)
8890
inc.EmitUpdate(inc.Last())
8991
}
9092
} else {
9193
k := allKLines[len(allKLines)-1]
92-
inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64())
94+
inc.PushK(k)
9395
inc.EmitUpdate(inc.Last())
9496
}
9597
}
@@ -99,7 +101,7 @@ func (inc *CCI) handleKLineWindowUpdate(interval types.Interval, window types.KL
99101
return
100102
}
101103

102-
inc.calculateAndUpdate(window)
104+
inc.CalculateAndUpdate(window)
103105
}
104106

105107
func (inc *CCI) Bind(updater KLineWindowUpdater) {

pkg/indicator/cma.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,13 @@ func (inc *CA) Length() int {
4848

4949
var _ types.SeriesExtend = &CA{}
5050

51-
func (inc *CA) calculateAndUpdate(allKLines []types.KLine) {
51+
func (inc *CA) PushK(k types.KLine) {
52+
inc.Update(k.Close.Float64())
53+
}
54+
55+
func (inc *CA) CalculateAndUpdate(allKLines []types.KLine) {
5256
for _, k := range allKLines {
53-
inc.Update(k.Close.Float64())
57+
inc.PushK(k)
5458
inc.EmitUpdate(inc.Last())
5559
}
5660
}
@@ -60,7 +64,7 @@ func (inc *CA) handleKLineWindowUpdate(interval types.Interval, window types.KLi
6064
return
6165
}
6266

63-
inc.calculateAndUpdate(window)
67+
inc.CalculateAndUpdate(window)
6468
}
6569

6670
func (inc *CA) Bind(updater KLineWindowUpdater) {

pkg/indicator/const.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package indicator
2+
3+
import (
4+
"time"
5+
6+
"github.com/c9s/bbgo/pkg/fixedpoint"
7+
)
8+
9+
var three = fixedpoint.NewFromInt(3)
10+
11+
var zeroTime = time.Time{}
12+

pkg/indicator/dema.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,20 @@ func (inc *DEMA) Length() int {
5050

5151
var _ types.SeriesExtend = &DEMA{}
5252

53-
func (inc *DEMA) calculateAndUpdate(allKLines []types.KLine) {
53+
func (inc *DEMA) PushK(k types.KLine) {
54+
inc.Update(k.Close.Float64())
55+
}
56+
57+
func (inc *DEMA) CalculateAndUpdate(allKLines []types.KLine) {
5458
if inc.a1 == nil {
5559
for _, k := range allKLines {
56-
inc.Update(k.Close.Float64())
60+
inc.PushK(k)
5761
inc.EmitUpdate(inc.Last())
5862
}
5963
} else {
60-
inc.Update(allKLines[len(allKLines)-1].Close.Float64())
64+
// last k
65+
k := allKLines[len(allKLines)-1]
66+
inc.PushK(k)
6167
inc.EmitUpdate(inc.Last())
6268
}
6369
}
@@ -67,7 +73,7 @@ func (inc *DEMA) handleKLineWindowUpdate(interval types.Interval, window types.K
6773
return
6874
}
6975

70-
inc.calculateAndUpdate(window)
76+
inc.CalculateAndUpdate(window)
7177
}
7278

7379
func (inc *DEMA) Bind(updater KLineWindowUpdater) {

pkg/indicator/dema_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func Test_DEMA(t *testing.T) {
4545
for _, tt := range tests {
4646
t.Run(tt.name, func(t *testing.T) {
4747
dema := DEMA{IntervalWindow: types.IntervalWindow{Window: 16}}
48-
dema.calculateAndUpdate(tt.kLines)
48+
dema.CalculateAndUpdate(tt.kLines)
4949
last := dema.Last()
5050
assert.InDelta(t, tt.want, last, Delta)
5151
assert.InDelta(t, tt.next, dema.Index(1), Delta)

pkg/indicator/dmi.go

+15-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
//go:generate callbackgen -type DMI
1616
type DMI struct {
1717
types.IntervalWindow
18+
1819
ADXSmoothing int
1920
atr *ATR
2021
DMP types.UpdatableSeriesExtend
@@ -23,7 +24,8 @@ type DMI struct {
2324
DIMinus *types.Queue
2425
ADX types.UpdatableSeriesExtend
2526
PrevHigh, PrevLow float64
26-
UpdateCallbacks []func(diplus, diminus, adx float64)
27+
28+
updateCallbacks []func(diplus, diminus, adx float64)
2729
}
2830

2931
func (inc *DMI) Update(high, low, cloze float64) {
@@ -32,6 +34,7 @@ func (inc *DMI) Update(high, low, cloze float64) {
3234
inc.DMN = &RMA{IntervalWindow: inc.IntervalWindow, Adjust: true}
3335
inc.ADX = &RMA{IntervalWindow: types.IntervalWindow{Window: inc.ADXSmoothing}, Adjust: true}
3436
}
37+
3538
if inc.atr == nil {
3639
inc.atr = &ATR{IntervalWindow: inc.IntervalWindow}
3740
inc.atr.Update(high, low, cloze)
@@ -41,6 +44,7 @@ func (inc *DMI) Update(high, low, cloze float64) {
4144
inc.DIMinus = types.NewQueue(500)
4245
return
4346
}
47+
4448
inc.atr.Update(high, low, cloze)
4549
up := high - inc.PrevHigh
4650
dn := inc.PrevLow - low
@@ -87,15 +91,20 @@ func (inc *DMI) Length() int {
8791
return inc.ADX.Length()
8892
}
8993

90-
func (inc *DMI) calculateAndUpdate(allKLines []types.KLine) {
94+
func (inc *DMI) PushK(k types.KLine) {
95+
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
96+
}
97+
98+
func (inc *DMI) CalculateAndUpdate(allKLines []types.KLine) {
99+
last := allKLines[len(allKLines)-1]
100+
91101
if inc.ADX == nil {
92102
for _, k := range allKLines {
93-
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
103+
inc.PushK(k)
94104
inc.EmitUpdate(inc.DIPlus.Last(), inc.DIMinus.Last(), inc.ADX.Last())
95105
}
96106
} else {
97-
k := allKLines[len(allKLines)-1]
98-
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
107+
inc.PushK(last)
99108
inc.EmitUpdate(inc.DIPlus.Last(), inc.DIMinus.Last(), inc.ADX.Last())
100109
}
101110
}
@@ -105,7 +114,7 @@ func (inc *DMI) handleKLineWindowUpdate(interval types.Interval, window types.KL
105114
return
106115
}
107116

108-
inc.calculateAndUpdate(window)
117+
inc.CalculateAndUpdate(window)
109118
}
110119

111120
func (inc *DMI) Bind(updater KLineWindowUpdater) {

0 commit comments

Comments
 (0)