bbgo/doc/development/indicator.md

212 lines
5.7 KiB
Markdown

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.
We could refer to the live-data without the worriedness of handling market data subscription.
To use the builtin indicators, call `Indicators(symbol)` method to get the indicator set of a symbol,
```go
session.Indicators(symbol string) *IndicatorSet
```
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
kLines := indicatorv2.KLines(stream, "BTCUSDT", types.Interval1m)
```
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.
To get the closed prices from the kLines stream, simply pass the kLines stream instance to a ClosedPrice indicator:
```go
closePrices := indicatorv2.ClosePrices(kLines)
```
When the kLine indicator pushes a new kline, the ClosePrices stream receives a kLine object then gets the closed price
from the kLine object.
to get the latest value of an indicator (closePrices), use Last(n) method, where n starts from 0:
```go
lastClosedPrice := closePrices.Last(0)
secondClosedPrice := closePrices.Last(1)
```
To create an EMA indicator instance, again, simply pass the closePrice indicator to the SMA stream constructor:
```go
ema := indicatorv2.EMA(closePrices, 17)
```
If you want to listen to the EMA value events, add a callback on the indicator instance:
```go
ema.OnUpdate(func(v float64) { .... })
```
To combine these techniques together:
```go
// use dot import to use the constructors as helpers
import . "git.qtrade.icu/lychiyu/bbgo/pkg/indicator/v2"
func main() {
// you should get the correct stream instance from the *bbgo.ExchangeSession instance
stream := &types.Stream{}
ema := EMA(
ClosePrices(
KLines(stream, types.Interval1m)), 14)
_ = ema
}
```
## Adding New Indicator
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 will be a float64 value indicator, we will use `*types.Float64Series` here to store our values,
`types.Float64Series` is a struct that contains a slice to store the float64 values, it is implemented as follows:
```go
type Float64Series struct {
SeriesBase
Float64Updater
Slice floats.Slice
}
```
The `Slice` field is a []float64 slice,
which provides some helper methods that helps you do some calculation on the float64 slice.
And Float64Updater provides a way to let indicators subscribe to the updated value:
```go
u := Float64Updater{}
u.OnUpdate(func(v float64) { ... })
u.OnUpdate(func(v float64) { ... })
// to emit the callbacks
u.EmitUpdate(10.0)
```
Now you have a basic concept about the Float64Series,
we can now start to implement our first indicator structure:
```go
package indicatorv2
type EWMAStream struct {
// embedded struct to inherit Float64Series methods
*types.Float64Series
// parameters we need
window int
multiplier float64
}
```
Again, since it is a float64 value indicator, we use `*types.Float64Series` here to store our values.
And then, add the constructor of the indicator stream:
```go
// the "source" here is your value source
func EWMA(source types.Float64Source, window int) *EWMAStream {
s := &EWMAStream{
Float64Series: types.NewFloat64Series(),
window: window,
multiplier: 2.0 / float64(1+window),
}
s.Bind(source, s)
return s
}
```
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.
The Bind method invokes the `Calculate()` method to obtain the updated value from a callback of the upstream source.
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
}
```