fix: ad NaN error. feature: Line indicator init functions. feature: indicator manual

This commit is contained in:
zenix 2022-04-04 18:19:17 +09:00
parent 567e7bd214
commit e171101d90
8 changed files with 147 additions and 16 deletions

View File

@ -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`.

View File

@ -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 {

View File

@ -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{}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {