From e171101d9062e1472126c2e709925740ef6edb70 Mon Sep 17 00:00:00 2001 From: zenix Date: Mon, 4 Apr 2022 18:19:17 +0900 Subject: [PATCH] fix: ad NaN error. feature: Line indicator init functions. feature: indicator manual --- doc/development/indicator.md | 100 +++++++++++++++++++++++++++++++++++ pkg/indicator/ad.go | 13 +++-- pkg/indicator/line.go | 26 +++++++++ pkg/indicator/rsi.go | 4 +- pkg/indicator/sma.go | 4 +- pkg/indicator/stoch.go | 8 +-- pkg/indicator/vwap.go | 4 +- pkg/indicator/vwma.go | 4 +- 8 files changed, 147 insertions(+), 16 deletions(-) create mode 100644 doc/development/indicator.md diff --git a/doc/development/indicator.md b/doc/development/indicator.md new file mode 100644 index 0000000000..b6c1c72e2f --- /dev/null +++ b/doc/development/indicator.md @@ -0,0 +1,100 @@ +How To Use Builtin Indicators and Create New Indicators +------------------------------------------------------- + +### Built-in Indicators +In bbgo session, we already have several indicators defined inside. +We could refer to the live-data without the worriedness of handling market data subscription. +To use the builtin ones, we could refer the `StandardIndicatorSet` type: + +```go +// defined in pkg/bbgo/session.go +(*StandardIndicatorSet) BOLL(iw types.IntervalWindow, bandwidth float64) *indicator.BOLL +(*StandardIndicatorSet) SMA(iw types.IntervalWindow) *indicator.SMA +(*StandardIndicatorSet) EWMA(iw types.IntervalWindow) *indicator.EWMA +(*StandardIndicatorSet) STOCH(iw types.IntervalWindow) *indicator.STOCH +(*StandardIndicatorSet) VOLATILITY(iw types.IntervalWindow) *indicator.VOLATILITY +``` + +and to get the `*StandardIndicatorSet` from `ExchangeSession`, just need to call: +```go +indicatorSet, ok := session.StandardIndicatorSet("BTCUSDT") // param: symbol +``` +in your strategy's `Run` function. + + +And in `Subscribe` function in strategy, just subscribe the `KLineChannel` on the interval window of the indicator you want to query, you should be able to acquire the latest number on the indicators. + +However, what if you want to use the indicators not defined in `StandardIndicatorSet`? For example, the `AD` indicator defined in `pkg/indicators/ad.go`? + +Here's a simple example in what you should write in your strategy code: +```go +import ( + "context" + "fmt" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/types" + "github.com/c9s/bbgo/pkg/indicator" +) + +type Strategy struct {} + +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { + session.Subscribe(types.KLineChannel, s.Symbol. types.SubscribeOptions{Interval: "1m"}) +} + +func (s *Strategy) Run(ctx context.Context, oe bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + // first we need to get market data store(cached market data) from the exchange session + st, ok := session.MarketDataStore(s.Symbol) + if !ok { + ... + return err + } + // setup the time frame size + window := types.IntervalWindow{Window: 10, Interval: types.Interval1m} + // construct AD indicator + AD := &indicator.AD{IntervalWindow: window} + // bind indicator to the data store, so that our callback could be triggered + AD.Bind(st) + AD.OnUpdate(func (ad float64) { + fmt.Printf("now we've got ad: %f, total length: %d\n", ad, AD.Length()) + }) +} +``` + +#### To Contribute + +try to create new indicators in `pkg/indicator/` folder, and add compilation hint of go generator: +```go +// go:generate callbackgen -type StructName +type StructName struct { + ... + UpdateCallbacks []func(value float64) +} + +``` +And implement required interface methods: +```go +// custom function +func (inc *StructName) calculateAndUpdate(kLines []types.KLine) { + // calculation... + // assign the result to calculatedValue + inc.EmitUpdate(calculatedValue) // produce data, broadcast to the subscribers +} + +// custom function +func (inc *StructName) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + // filter on interval + inc.calculateAndUpdate(window) +} + +// required +func (inc *StructName) Bind(updator KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} +``` + +The `KLineWindowUpdater` interface is currently defined in `pkg/indicator/ewma.go` and may be moved out in the future. + +Once the implementation is done, run `go generate` to generate the callback functions of the indicator. +You should be able to implement your strategy and use the new indicator in the same way as `AD`. diff --git a/pkg/indicator/ad.go b/pkg/indicator/ad.go index 17defe9d03..efbce06735 100644 --- a/pkg/indicator/ad.go +++ b/pkg/indicator/ad.go @@ -23,12 +23,17 @@ type AD struct { } func (inc *AD) Update(kLine types.KLine) { - close := kLine.Close.Float64() + cloze := kLine.Close.Float64() high := kLine.High.Float64() low := kLine.Low.Float64() volume := kLine.Volume.Float64() - moneyFlowVolume := ((2*close - high - low) / (high - low)) * volume + var moneyFlowVolume float64 + if high == low { + moneyFlowVolume = 0 + } else { + moneyFlowVolume = ((2*cloze - high - low) / (high - low)) * volume + } ad := inc.Last() + moneyFlowVolume inc.Values.Push(ad) @@ -43,10 +48,10 @@ func (inc *AD) Last() float64 { func (inc *AD) Index(i int) float64 { length := len(inc.Values) - if length == 0 || length - i - 1 < 0 { + if length == 0 || length-i-1 < 0 { return 0 } - return inc.Values[length - i - 1] + return inc.Values[length-i-1] } func (inc *AD) Length() int { diff --git a/pkg/indicator/line.go b/pkg/indicator/line.go index 8518a499ee..3e37685776 100644 --- a/pkg/indicator/line.go +++ b/pkg/indicator/line.go @@ -6,6 +6,11 @@ import ( "github.com/c9s/bbgo/pkg/types" ) +// Line indicator is a utility that helps to simulate either the +// 1. trend +// 2. support +// 3. resistance +// of the market data, defined with series interface type Line struct { types.IntervalWindow start float64 @@ -39,4 +44,25 @@ func (l *Line) Length() int { return int(l.startTime.Sub(l.currentTime).Minutes()) / l.Interval.Minutes() } +func (l *Line) SetX1(value float64, startTime time.Time) { + l.startTime = startTime + l.start = value +} + +func (l *Line) SetX2(value float64, endTime time.Time) { + l.endTime = endTime + l.end = value +} + +func NewLine(startValue float64, startTime time.Time, endValue float64, endTime time.Time, interval types.Interval) *Line { + return &Line{ + start: startValue, + end: endValue, + startTime: startTime, + endTime: endTime, + currentTime: endTime, + Interval: interval, + } +} + var _ types.Series = &Line{} diff --git a/pkg/indicator/rsi.go b/pkg/indicator/rsi.go index 430c9ac029..76edc2f583 100644 --- a/pkg/indicator/rsi.go +++ b/pkg/indicator/rsi.go @@ -65,10 +65,10 @@ func (inc *RSI) Last() float64 { func (inc *RSI) Index(i int) float64 { length := len(inc.Values) - if length <= 0 || length - i - 1 < 0 { + if length <= 0 || length-i-1 < 0 { return 0.0 } - return inc.Values[length - i - 1] + return inc.Values[length-i-1] } func (inc *RSI) Length() int { diff --git a/pkg/indicator/sma.go b/pkg/indicator/sma.go index 7dc428acff..a4fbca5629 100644 --- a/pkg/indicator/sma.go +++ b/pkg/indicator/sma.go @@ -32,11 +32,11 @@ func (inc *SMA) Last() float64 { func (inc *SMA) Index(i int) float64 { length := len(inc.Values) - if length == 0 || length - i - 1 < 0 { + if length == 0 || length-i-1 < 0 { return 0.0 } - return inc.Values[length - i - 1] + return inc.Values[length-i-1] } func (inc *SMA) Length() int { diff --git a/pkg/indicator/stoch.go b/pkg/indicator/stoch.go index 1c9648c971..6df4681567 100644 --- a/pkg/indicator/stoch.go +++ b/pkg/indicator/stoch.go @@ -105,10 +105,10 @@ func (inc *DSeries) Length() int { func (inc *DSeries) Index(i int) float64 { length := len(inc.D) - if length == 0 || length - i - 1 < 0 { + if length == 0 || length-i-1 < 0 { return 0 } - return inc.D[length - i - 1] + return inc.D[length-i-1] } var _ types.Series = &DSeries{} @@ -123,10 +123,10 @@ func (inc *KSeries) Last() float64 { func (inc *KSeries) Index(i int) float64 { length := len(inc.K) - if length == 0 || length - i - 1 < 0 { + if length == 0 || length-i-1 < 0 { return 0 } - return inc.K[length - i - 1] + return inc.K[length-i-1] } func (inc *KSeries) Length() int { diff --git a/pkg/indicator/vwap.go b/pkg/indicator/vwap.go index 374f99a7a4..93ff7ff5b8 100644 --- a/pkg/indicator/vwap.go +++ b/pkg/indicator/vwap.go @@ -37,11 +37,11 @@ func (inc *VWAP) Last() float64 { func (inc *VWAP) Index(i int) float64 { length := len(inc.Values) - if length == 0 || length - i - 1 < 0 { + if length == 0 || length-i-1 < 0 { return 0 } - return inc.Values[length - i - 1] + return inc.Values[length-i-1] } func (inc *VWAP) Length() int { diff --git a/pkg/indicator/vwma.go b/pkg/indicator/vwma.go index f3e09d2aa3..131e2f5df0 100644 --- a/pkg/indicator/vwma.go +++ b/pkg/indicator/vwma.go @@ -36,10 +36,10 @@ func (inc *VWMA) Last() float64 { func (inc *VWMA) Index(i int) float64 { length := len(inc.Values) - if length == 0 || length - i - 1 < 0 { + if length == 0 || length-i-1 < 0 { return 0 } - return inc.Values[length - i - 1] + return inc.Values[length-i-1] } func (inc *VWMA) Length() int {