mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
doc: update indicator documents
This commit is contained in:
parent
4bd5c62646
commit
925590cf27
128
doc/development/indicator-v1.md
Normal file
128
doc/development/indicator-v1.md
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
How To Use Builtin Indicators and Create New Indicators
|
||||||
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
**NOTE THAT V1 INDICATOR WILL BE DEPRECATED, USE V2 INDICATOR INSTEAD**
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
func (inc *StructName) Update(value float64) {
|
||||||
|
// indicator calculation here...
|
||||||
|
// push value...
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *StructName) PushK(k types.KLine) {
|
||||||
|
inc.Update(k.Close.Float64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom function
|
||||||
|
func (inc *StructName) CalculateAndUpdate(kLines []types.KLine) {
|
||||||
|
if len(inc.Values) == 0 {
|
||||||
|
// preload or initialization
|
||||||
|
for _, k := range allKLines {
|
||||||
|
inc.PushK(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
inc.EmitUpdate(inc.Last())
|
||||||
|
} else {
|
||||||
|
// update new value only
|
||||||
|
k := allKLines[len(allKLines)-1]
|
||||||
|
inc.PushK(k)
|
||||||
|
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`.
|
||||||
|
|
||||||
|
#### Generalize
|
||||||
|
|
||||||
|
In order to provide indicator users a lower learning curve, we've designed the `types.Series` interface. We recommend indicator developers to also implement the `types.Series` interface to provide richer functionality on the computed result. To have deeper understanding how `types.Series` works, please refer to [doc/development/series.md](./series.md)
|
|
@ -1,124 +1,163 @@
|
||||||
How To Use Builtin Indicators and Create New Indicators
|
How To Use Builtin Indicators and Add New Indicators (V2)
|
||||||
-------------------------------------------------------
|
=========================================================
|
||||||
|
|
||||||
|
## Using Built-in Indicators
|
||||||
|
|
||||||
|
In bbgo session, we already have several built-in indicators defined.
|
||||||
|
|
||||||
### 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.
|
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:
|
To use the builtin indicators, call `Indicators(symbol)` method to get the indicator set of a symbol,
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// defined in pkg/bbgo/session.go
|
session.Indicators(symbol string) *IndicatorSet
|
||||||
(*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:
|
IndicatorSet is a helper that helps you construct the indicator instance.
|
||||||
|
Each indicator is a stream that subscribes to
|
||||||
|
an upstream through the callback.
|
||||||
|
|
||||||
|
We will explain how the indicator works from scratch in the following section.
|
||||||
|
|
||||||
|
The following code will create a kLines stream that subscribes to specific klines from a websocket stream instance:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
indicatorSet, ok := session.StandardIndicatorSet("BTCUSDT") // param: symbol
|
kLines := indicatorv2.KLines(stream, "BTCUSDT", types.Interval1m)
|
||||||
```
|
```
|
||||||
in your strategy's `Run` function.
|
|
||||||
|
|
||||||
|
The kLines stream is a special indicator stream that subscribes to the kLine event from the websocket stream. It
|
||||||
|
registers a callback on `OnKLineClosed`, and when there is a kLine event triggered, it pushes the kLines into its
|
||||||
|
series, and then it triggers all the subscribers that subscribe to it.
|
||||||
|
|
||||||
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.
|
To get the closed prices from the kLines stream, simply pass the kLines stream instance to a ClosedPrice indicator:
|
||||||
|
|
||||||
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
|
```go
|
||||||
import (
|
closePrices := indicatorv2.ClosePrices(kLines)
|
||||||
"context"
|
```
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
When the kLine indicator pushes a new kline, the ClosePrices stream receives a kLine object then gets the closed price
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
from the kLine object.
|
||||||
"github.com/c9s/bbgo/pkg/indicator"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Strategy struct {}
|
to get the latest value of an indicator (closePrices), use Last(n) method, where n starts from 0:
|
||||||
|
|
||||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
```go
|
||||||
session.Subscribe(types.KLineChannel, s.Symbol. types.SubscribeOptions{Interval: "1m"})
|
lastClosedPrice := closePrices.Last(0)
|
||||||
}
|
secondClosedPrice := closePrices.Last(1)
|
||||||
|
```
|
||||||
|
|
||||||
func (s *Strategy) Run(ctx context.Context, oe bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
To create a EMA indicator instance, again, simply pass the closePrice indicator to the SMA stream constructor:
|
||||||
// first we need to get market data store(cached market data) from the exchange session
|
|
||||||
st, ok := session.MarketDataStore(s.Symbol)
|
```go
|
||||||
if !ok {
|
ema := indicatorv2.EMA(closePrices, 17)
|
||||||
...
|
```
|
||||||
return err
|
|
||||||
}
|
If you want to listen to the EMA value events, just add a callback on the indicator instance:
|
||||||
// setup the time frame size
|
|
||||||
window := types.IntervalWindow{Window: 10, Interval: types.Interval1m}
|
```go
|
||||||
// construct AD indicator
|
ema.OnUpdate(func(v float64) { .... })
|
||||||
AD := &indicator.AD{IntervalWindow: window}
|
```
|
||||||
// bind indicator to the data store, so that our callback could be triggered
|
|
||||||
AD.Bind(st)
|
## Adding New Indicator
|
||||||
AD.OnUpdate(func (ad float64) {
|
|
||||||
fmt.Printf("now we've got ad: %f, total length: %d\n", ad, AD.Length())
|
Adding a new indicator is pretty straightforward. Simply create a new struct and insert the necessary parameters as
|
||||||
})
|
struct fields.
|
||||||
|
|
||||||
|
The indicator algorithm will be implemented in the `Calculate(v float64) float64` method.
|
||||||
|
|
||||||
|
You can think of it as a simple input-output model: it takes a float64 number as input, calculates the value, and
|
||||||
|
returns a float64 number as output.
|
||||||
|
|
||||||
|
```
|
||||||
|
[input float64] -> [Calculate] -> [output float64]
|
||||||
|
```
|
||||||
|
|
||||||
|
Since it is a float64 value indicator, we will use `*types.Float64Series` here to store our values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package indicatorv2
|
||||||
|
|
||||||
|
type EWMAStream struct {
|
||||||
|
// embedded struct to inherit Float64Series methods
|
||||||
|
*types.Float64Series
|
||||||
|
|
||||||
|
// parameters we need
|
||||||
|
window int
|
||||||
|
multiplier float64
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### To Contribute
|
And then, add the constructor of the indicator stream:
|
||||||
|
|
||||||
try to create new indicators in `pkg/indicator/` folder, and add compilation hint of go generator:
|
|
||||||
```go
|
```go
|
||||||
// go:generate callbackgen -type StructName
|
// the "source" here is your value source
|
||||||
type StructName struct {
|
func EWMA(source types.Float64Source, window int) *EWMAStream {
|
||||||
...
|
s := &EWMAStream{
|
||||||
updateCallbacks []func(value float64)
|
Float64Series: types.NewFloat64Series(),
|
||||||
|
window: window,
|
||||||
|
multiplier: 2.0 / float64(1+window),
|
||||||
}
|
}
|
||||||
|
s.Bind(source, s)
|
||||||
```
|
return s
|
||||||
And implement required interface methods:
|
|
||||||
```go
|
|
||||||
|
|
||||||
func (inc *StructName) Update(value float64) {
|
|
||||||
// indicator calculation here...
|
|
||||||
// push value...
|
|
||||||
}
|
|
||||||
|
|
||||||
func (inc *StructName) PushK(k types.KLine) {
|
|
||||||
inc.Update(k.Close.Float64())
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom function
|
|
||||||
func (inc *StructName) CalculateAndUpdate(kLines []types.KLine) {
|
|
||||||
if len(inc.Values) == 0 {
|
|
||||||
// preload or initialization
|
|
||||||
for _, k := range allKLines {
|
|
||||||
inc.PushK(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
inc.EmitUpdate(inc.Last())
|
|
||||||
} else {
|
|
||||||
// update new value only
|
|
||||||
k := allKLines[len(allKLines)-1]
|
|
||||||
inc.PushK(k)
|
|
||||||
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.
|
Where the source refers to your upstream value, such as closedPrices, openedPrices, or any type of float64 series. For
|
||||||
|
example, Volume could also serve as the source.
|
||||||
|
|
||||||
Once the implementation is done, run `go generate` to generate the callback functions of the indicator.
|
The Bind method invokes the `Calculate()` method to obtain the updated value from a callback of the upstream source.
|
||||||
You should be able to implement your strategy and use the new indicator in the same way as `AD`.
|
Subsequently, it calls EmitUpdate to activate the callbacks of its subscribers,
|
||||||
|
thereby passing the updated value to all of them.
|
||||||
|
|
||||||
|
Next, write your algorithm within the Calculate method:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *EWMAStream) Calculate(v float64) float64 {
|
||||||
|
// if you need the last number to calculate the next value
|
||||||
|
// call s.Slice.Last(0)
|
||||||
|
//
|
||||||
|
last := s.Slice.Last(0)
|
||||||
|
if last == 0.0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
m := s.multiplier
|
||||||
|
return (1.0-m)*last + m*v
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sometimes you might need to store the intermediate values inside your indicator, you can add the extra field with type Float64Series like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type EWMAStream struct {
|
||||||
|
// embedded struct to inherit Float64Series methods
|
||||||
|
*types.Float64Series
|
||||||
|
|
||||||
|
A *types.Float64Series
|
||||||
|
B *types.Float64Series
|
||||||
|
|
||||||
|
// parameters we need
|
||||||
|
window int
|
||||||
|
multiplier float64
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In your `Calculate()` method, you can push the values into these float64 series, for example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *EWMAStream) Calculate(v float64) float64 {
|
||||||
|
// if you need the last number to calculate the next value
|
||||||
|
// call s.Slice.Last(0)
|
||||||
|
last := s.Slice.Last(0)
|
||||||
|
if last == 0.0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you need to trigger callbacks, use PushAndEmit
|
||||||
|
s.A.Push(last / 2)
|
||||||
|
s.B.Push(last / 3)
|
||||||
|
|
||||||
|
m := s.multiplier
|
||||||
|
return (1.0-m)*last + m*v
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Generalize
|
|
||||||
|
|
||||||
In order to provide indicator users a lower learning curve, we've designed the `types.Series` interface. We recommend indicator developers to also implement the `types.Series` interface to provide richer functionality on the computed result. To have deeper understanding how `types.Series` works, please refer to [doc/development/series.md](./series.md)
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
Indicator Interface
|
Series Interface
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
In bbgo, we've added several interfaces to standardize the indicator protocol.
|
Series defines the data structure of the indicator data.
|
||||||
The new interfaces will allow strategy developers switching similar indicators without checking the code.
|
|
||||||
|
indicators use this series interface to manage these time-series data.
|
||||||
|
|
||||||
|
The interface allow strategy developers to switch similar indicators without checking the code.
|
||||||
|
|
||||||
Signal contributors or indicator developers were also able to be benefit from the existing interface functions, such as `Add`, `Mul`, `Minus`, and `Div`, without rebuilding the wheels.
|
Signal contributors or indicator developers were also able to be benefit from the existing interface functions, such as `Add`, `Mul`, `Minus`, and `Div`, without rebuilding the wheels.
|
||||||
|
|
||||||
The series interface in bbgo borrows the concept of `series` type in pinescript that allow us to query data in time-based reverse order (data that created later will be the former object in series). Right now, based on the return type, we have two interfaces been defined in [pkg/types/indicator.go](../../pkg/types/indicator.go):
|
The series interface in bbgo borrows the concept of `series` type in pinescript that allow us to query data in time-based reverse order (data that created later will be the former object in series). Right now, based on the return type, we have two interfaces been defined in [pkg/types/indicator.go](../../pkg/types/indicator.go):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user