mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 08:15:15 +00:00
Merge pull request #505 from zenixls2/feature/series
feature: add pinescript series interface
This commit is contained in:
commit
b57c94fe12
100
doc/development/indicator.md
Normal file
100
doc/development/indicator.md
Normal 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`.
|
|
@ -11,9 +11,9 @@ type MarketDataStore struct {
|
||||||
Symbol string
|
Symbol string
|
||||||
|
|
||||||
// KLineWindows stores all loaded klines per interval
|
// KLineWindows stores all loaded klines per interval
|
||||||
KLineWindows map[types.Interval]types.KLineWindow `json:"-"`
|
KLineWindows map[types.Interval]*types.KLineWindow `json:"-"`
|
||||||
|
|
||||||
kLineWindowUpdateCallbacks []func(interval types.Interval, kline types.KLineWindow)
|
kLineWindowUpdateCallbacks []func(interval types.Interval, klines types.KLineWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMarketDataStore(symbol string) *MarketDataStore {
|
func NewMarketDataStore(symbol string) *MarketDataStore {
|
||||||
|
@ -21,16 +21,16 @@ func NewMarketDataStore(symbol string) *MarketDataStore {
|
||||||
Symbol: symbol,
|
Symbol: symbol,
|
||||||
|
|
||||||
// KLineWindows stores all loaded klines per interval
|
// KLineWindows stores all loaded klines per interval
|
||||||
KLineWindows: make(map[types.Interval]types.KLineWindow, len(types.SupportedIntervals)), // 12 interval, 1m,5m,15m,30m,1h,2h,4h,6h,12h,1d,3d,1w
|
KLineWindows: make(map[types.Interval]*types.KLineWindow, len(types.SupportedIntervals)), // 12 interval, 1m,5m,15m,30m,1h,2h,4h,6h,12h,1d,3d,1w
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MarketDataStore) SetKLineWindows(windows map[types.Interval]types.KLineWindow) {
|
func (store *MarketDataStore) SetKLineWindows(windows map[types.Interval]*types.KLineWindow) {
|
||||||
store.KLineWindows = windows
|
store.KLineWindows = windows
|
||||||
}
|
}
|
||||||
|
|
||||||
// KLinesOfInterval returns the kline window of the given interval
|
// KLinesOfInterval returns the kline window of the given interval
|
||||||
func (store *MarketDataStore) KLinesOfInterval(interval types.Interval) (kLines types.KLineWindow, ok bool) {
|
func (store *MarketDataStore) KLinesOfInterval(interval types.Interval) (kLines *types.KLineWindow, ok bool) {
|
||||||
kLines, ok = store.KLineWindows[interval]
|
kLines, ok = store.KLineWindows[interval]
|
||||||
return kLines, ok
|
return kLines, ok
|
||||||
}
|
}
|
||||||
|
@ -50,14 +50,15 @@ func (store *MarketDataStore) handleKLineClosed(kline types.KLine) {
|
||||||
func (store *MarketDataStore) AddKLine(kline types.KLine) {
|
func (store *MarketDataStore) AddKLine(kline types.KLine) {
|
||||||
window, ok := store.KLineWindows[kline.Interval]
|
window, ok := store.KLineWindows[kline.Interval]
|
||||||
if !ok {
|
if !ok {
|
||||||
window = make(types.KLineWindow, 0, 1000)
|
var tmp = make(types.KLineWindow, 0, 1000)
|
||||||
|
store.KLineWindows[kline.Interval] = &tmp
|
||||||
|
window = &tmp
|
||||||
}
|
}
|
||||||
window.Add(kline)
|
window.Add(kline)
|
||||||
|
|
||||||
if len(window) > MaxNumOfKLines {
|
if len(*window) > MaxNumOfKLines {
|
||||||
window = window[MaxNumOfKLinesTruncate-1:]
|
*window = (*window)[MaxNumOfKLinesTruncate-1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
store.KLineWindows[kline.Interval] = window
|
store.EmitKLineWindowUpdate(kline.Interval, *window)
|
||||||
store.EmitKLineWindowUpdate(kline.Interval, window)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (store *MarketDataStore) OnKLineWindowUpdate(cb func(interval types.Interval, kline types.KLineWindow)) {
|
func (store *MarketDataStore) OnKLineWindowUpdate(cb func(interval types.Interval, klines types.KLineWindow)) {
|
||||||
store.kLineWindowUpdateCallbacks = append(store.kLineWindowUpdateCallbacks, cb)
|
store.kLineWindowUpdateCallbacks = append(store.kLineWindowUpdateCallbacks, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MarketDataStore) EmitKLineWindowUpdate(interval types.Interval, kline types.KLineWindow) {
|
func (store *MarketDataStore) EmitKLineWindowUpdate(interval types.Interval, klines types.KLineWindow) {
|
||||||
for _, cb := range store.kLineWindowUpdateCallbacks {
|
for _, cb := range store.kLineWindowUpdateCallbacks {
|
||||||
cb(interval, kline)
|
cb(interval, klines)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -365,7 +365,7 @@ var BacktestCmd = &cobra.Command{
|
||||||
|
|
||||||
startPrice, ok := session.StartPrice(symbol)
|
startPrice, ok := session.StartPrice(symbol)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("start price not found: %s, %s", symbol, exchangeName)
|
return fmt.Errorf("start price not found: %s, %s. run --sync first", symbol, exchangeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPrice, ok := session.LastPrice(symbol)
|
lastPrice, ok := session.LastPrice(symbol)
|
||||||
|
|
|
@ -23,12 +23,17 @@ type AD struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inc *AD) Update(kLine types.KLine) {
|
func (inc *AD) Update(kLine types.KLine) {
|
||||||
close := kLine.Close.Float64()
|
cloze := kLine.Close.Float64()
|
||||||
high := kLine.High.Float64()
|
high := kLine.High.Float64()
|
||||||
low := kLine.Low.Float64()
|
low := kLine.Low.Float64()
|
||||||
volume := kLine.Volume.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
|
ad := inc.Last() + moneyFlowVolume
|
||||||
inc.Values.Push(ad)
|
inc.Values.Push(ad)
|
||||||
|
@ -41,6 +46,20 @@ func (inc *AD) Last() float64 {
|
||||||
return inc.Values[len(inc.Values)-1]
|
return inc.Values[len(inc.Values)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inc *AD) Index(i int) float64 {
|
||||||
|
length := len(inc.Values)
|
||||||
|
if length == 0 || length-i-1 < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return inc.Values[length-i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *AD) Length() int {
|
||||||
|
return len(inc.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.Series = &AD{}
|
||||||
|
|
||||||
func (inc *AD) calculateAndUpdate(kLines []types.KLine) {
|
func (inc *AD) calculateAndUpdate(kLines []types.KLine) {
|
||||||
for _, k := range kLines {
|
for _, k := range kLines {
|
||||||
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
|
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
|
||||||
|
|
|
@ -39,6 +39,24 @@ type BOLL struct {
|
||||||
updateCallbacks []func(sma, upBand, downBand float64)
|
updateCallbacks []func(sma, upBand, downBand float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BandType int
|
||||||
|
|
||||||
|
func (inc *BOLL) GetUpBand() types.Series {
|
||||||
|
return &inc.UpBand
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *BOLL) GetDownBand() types.Series {
|
||||||
|
return &inc.DownBand
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *BOLL) GetSMA() types.Series {
|
||||||
|
return &inc.SMA
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *BOLL) GetStdDev() types.Series {
|
||||||
|
return &inc.StdDev
|
||||||
|
}
|
||||||
|
|
||||||
func (inc *BOLL) LastUpBand() float64 {
|
func (inc *BOLL) LastUpBand() float64 {
|
||||||
if len(inc.UpBand) == 0 {
|
if len(inc.UpBand) == 0 {
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
|
@ -44,6 +44,18 @@ func (inc *EWMA) Last() float64 {
|
||||||
return inc.Values[len(inc.Values)-1]
|
return inc.Values[len(inc.Values)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inc *EWMA) Index(i int) float64 {
|
||||||
|
if i >= len(inc.Values) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return inc.Values[len(inc.Values)-1-i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *EWMA) Length() int {
|
||||||
|
return len(inc.Values)
|
||||||
|
}
|
||||||
|
|
||||||
func (inc *EWMA) calculateAndUpdate(allKLines []types.KLine) {
|
func (inc *EWMA) calculateAndUpdate(allKLines []types.KLine) {
|
||||||
if len(allKLines) < inc.Window {
|
if len(allKLines) < inc.Window {
|
||||||
// we can't calculate
|
// we can't calculate
|
||||||
|
@ -149,3 +161,5 @@ func (inc *EWMA) handleKLineWindowUpdate(interval types.Interval, window types.K
|
||||||
func (inc *EWMA) Bind(updater KLineWindowUpdater) {
|
func (inc *EWMA) Bind(updater KLineWindowUpdater) {
|
||||||
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ types.Series = &EWMA{}
|
||||||
|
|
76
pkg/indicator/line.go
Normal file
76
pkg/indicator/line.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package indicator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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
|
||||||
|
end float64
|
||||||
|
startIndex int
|
||||||
|
endIndex int
|
||||||
|
currentTime time.Time
|
||||||
|
Interval types.Interval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Line) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
|
||||||
|
if interval != l.Interval {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newTime := window.Last().EndTime.Time()
|
||||||
|
delta := int(newTime.Sub(l.currentTime).Minutes()) / l.Interval.Minutes()
|
||||||
|
l.startIndex += delta
|
||||||
|
l.endIndex += delta
|
||||||
|
l.currentTime = newTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Line) Bind(updater KLineWindowUpdater) {
|
||||||
|
updater.OnKLineWindowUpdate(l.handleKLineWindowUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Line) Last() float64 {
|
||||||
|
return (l.end-l.start) / float64(l.startIndex - l.endIndex) * float64(l.endIndex) + l.end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Line) Index(i int) float64 {
|
||||||
|
return (l.end-l.start) / float64(l.startIndex - l.endIndex) * float64(l.endIndex - i) + l.end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Line) Length() int {
|
||||||
|
if l.startIndex > l.endIndex {
|
||||||
|
return l.startIndex - l.endIndex
|
||||||
|
} else {
|
||||||
|
return l.endIndex - l.startIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Line) SetXY1(index int, value float64) {
|
||||||
|
l.startIndex = index
|
||||||
|
l.start = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Line) SetXY2(index int, value float64) {
|
||||||
|
l.endIndex = index
|
||||||
|
l.end = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLine(startIndex int, startValue float64, endIndex int, endValue float64, interval types.Interval) *Line {
|
||||||
|
return &Line{
|
||||||
|
start: startValue,
|
||||||
|
end: endValue,
|
||||||
|
startIndex: startIndex,
|
||||||
|
endIndex: endIndex,
|
||||||
|
currentTime: time.Time{},
|
||||||
|
Interval: interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.Series = &Line{}
|
|
@ -89,3 +89,34 @@ func (inc *MACD) handleKLineWindowUpdate(interval types.Interval, window types.K
|
||||||
func (inc *MACD) Bind(updater KLineWindowUpdater) {
|
func (inc *MACD) Bind(updater KLineWindowUpdater) {
|
||||||
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MACDValues struct {
|
||||||
|
*MACD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *MACDValues) Last() float64 {
|
||||||
|
if len(inc.Values) == 0 {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return inc.Values[len(inc.Values)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *MACDValues) Index(i int) float64 {
|
||||||
|
length := len(inc.Values)
|
||||||
|
if length == 0 || length-1-i < 0 {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return inc.Values[length-1+i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *MACDValues) Length() int {
|
||||||
|
return len(inc.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *MACD) MACD() types.Series {
|
||||||
|
return &MACDValues{inc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *MACD) Singals() types.Series {
|
||||||
|
return &inc.SignalLine
|
||||||
|
}
|
||||||
|
|
|
@ -63,6 +63,20 @@ func (inc *RSI) Last() float64 {
|
||||||
return inc.Values[len(inc.Values)-1]
|
return inc.Values[len(inc.Values)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inc *RSI) Index(i int) float64 {
|
||||||
|
length := len(inc.Values)
|
||||||
|
if length <= 0 || length-i-1 < 0 {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return inc.Values[length-i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *RSI) Length() int {
|
||||||
|
return len(inc.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.Series = &RSI{}
|
||||||
|
|
||||||
func (inc *RSI) calculateAndUpdate(kLines []types.KLine) {
|
func (inc *RSI) calculateAndUpdate(kLines []types.KLine) {
|
||||||
var priceF = KLineClosePriceMapper
|
var priceF = KLineClosePriceMapper
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,31 @@ func (inc *SMA) Last() float64 {
|
||||||
return inc.Values[len(inc.Values)-1]
|
return inc.Values[len(inc.Values)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inc *SMA) Index(i int) float64 {
|
||||||
|
length := len(inc.Values)
|
||||||
|
if length == 0 || length-i-1 < 0 {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
return inc.Values[length-i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *SMA) Length() int {
|
||||||
|
return len(inc.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.Series = &SMA{}
|
||||||
|
|
||||||
|
func (inc *SMA) Update(value float64) {
|
||||||
|
length := len(inc.Values)
|
||||||
|
if length == 0 {
|
||||||
|
inc.Values = append(inc.Values, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newVal := (inc.Values[length-1]*float64(inc.Window-1) + value) / float64(inc.Window)
|
||||||
|
inc.Values = append(inc.Values, newVal)
|
||||||
|
}
|
||||||
|
|
||||||
func (inc *SMA) calculateAndUpdate(kLines []types.KLine) {
|
func (inc *SMA) calculateAndUpdate(kLines []types.KLine) {
|
||||||
if len(kLines) < inc.Window {
|
if len(kLines) < inc.Window {
|
||||||
return
|
return
|
||||||
|
|
|
@ -82,3 +82,11 @@ func (inc *STOCH) handleKLineWindowUpdate(interval types.Interval, window types.
|
||||||
func (inc *STOCH) Bind(updater KLineWindowUpdater) {
|
func (inc *STOCH) Bind(updater KLineWindowUpdater) {
|
||||||
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inc *STOCH) GetD() types.Series {
|
||||||
|
return &inc.D
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *STOCH) GetK() types.Series {
|
||||||
|
return &inc.K
|
||||||
|
}
|
||||||
|
|
|
@ -35,6 +35,21 @@ func (inc *VWAP) Last() float64 {
|
||||||
return inc.Values[len(inc.Values)-1]
|
return inc.Values[len(inc.Values)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inc *VWAP) Index(i int) float64 {
|
||||||
|
length := len(inc.Values)
|
||||||
|
if length == 0 || length-i-1 < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return inc.Values[length-i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *VWAP) Length() int {
|
||||||
|
return len(inc.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.Series = &VWAP{}
|
||||||
|
|
||||||
func (inc *VWAP) Update(kLine types.KLine, priceF KLinePriceMapper) {
|
func (inc *VWAP) Update(kLine types.KLine, priceF KLinePriceMapper) {
|
||||||
price := priceF(kLine)
|
price := priceF(kLine)
|
||||||
volume := kLine.Volume.Float64()
|
volume := kLine.Volume.Float64()
|
||||||
|
|
|
@ -34,6 +34,20 @@ func (inc *VWMA) Last() float64 {
|
||||||
return inc.Values[len(inc.Values)-1]
|
return inc.Values[len(inc.Values)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inc *VWMA) Index(i int) float64 {
|
||||||
|
length := len(inc.Values)
|
||||||
|
if length == 0 || length-i-1 < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return inc.Values[length-i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *VWMA) Length() int {
|
||||||
|
return len(inc.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.Series = &VWMA{}
|
||||||
|
|
||||||
func KLinePriceVolumeMapper(k types.KLine) float64 {
|
func KLinePriceVolumeMapper(k types.KLine) float64 {
|
||||||
return k.Close.Mul(k.Volume).Float64()
|
return k.Close.Mul(k.Volume).Float64()
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,3 +116,29 @@ func (s Float64Slice) Dot(other Float64Slice) float64 {
|
||||||
func (s Float64Slice) Normalize() Float64Slice {
|
func (s Float64Slice) Normalize() Float64Slice {
|
||||||
return s.DivScalar(s.Sum())
|
return s.DivScalar(s.Sum())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Float64Slice) Last() float64 {
|
||||||
|
length := len(*a)
|
||||||
|
if length > 0 {
|
||||||
|
return (*a)[length-1]
|
||||||
|
}
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float64Slice) Index(i int) float64 {
|
||||||
|
length := len(*a)
|
||||||
|
if length-i < 0 || i < 0 {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return (*a)[length-i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float64Slice) Length() int {
|
||||||
|
return len(*a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Float64Slice) Addr() *Float64Slice {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Series = Float64Slice([]float64{}).Addr()
|
||||||
|
|
|
@ -1,6 +1,512 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"gonum.org/v1/gonum/stat"
|
||||||
|
)
|
||||||
|
|
||||||
// Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data.
|
// Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data.
|
||||||
type Float64Indicator interface {
|
type Float64Indicator interface {
|
||||||
Last() float64
|
Last() float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The interface maps to pinescript basic type `series`
|
||||||
|
// Access the internal historical data from the latest to the oldest
|
||||||
|
// Index(0) always maps to Last()
|
||||||
|
type Series interface {
|
||||||
|
Last() float64
|
||||||
|
Index(int) float64
|
||||||
|
Length() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// The interface maps to pinescript basic type `series` for bool type
|
||||||
|
// Access the internal historical data from the latest to the oldest
|
||||||
|
// Index(0) always maps to Last()
|
||||||
|
type BoolSeries interface {
|
||||||
|
Last() bool
|
||||||
|
Index(int) bool
|
||||||
|
Length() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate sum of the series
|
||||||
|
// if limit is given, will only sum first limit numbers (a.Index[0..limit])
|
||||||
|
// otherwise will sum all elements
|
||||||
|
func Sum(a Series, limit ...int) (sum float64) {
|
||||||
|
l := -1
|
||||||
|
if len(limit) > 0 {
|
||||||
|
l = limit[0]
|
||||||
|
}
|
||||||
|
if l < a.Length() {
|
||||||
|
l = a.Length()
|
||||||
|
}
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
sum += a.Index(i)
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the average value of the series
|
||||||
|
// if limit is given, will only calculate the average of first limit numbers (a.Index[0..limit])
|
||||||
|
// otherwise will operate on all elements
|
||||||
|
func Mean(a Series, limit ...int) (mean float64) {
|
||||||
|
l := -1
|
||||||
|
if len(limit) > 0 {
|
||||||
|
l = limit[0]
|
||||||
|
}
|
||||||
|
if l < a.Length() {
|
||||||
|
l = a.Length()
|
||||||
|
}
|
||||||
|
return Sum(a, l) / float64(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AbsResult struct {
|
||||||
|
a Series
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AbsResult) Last() float64 {
|
||||||
|
return math.Abs(a.a.Last())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AbsResult) Index(i int) float64 {
|
||||||
|
return math.Abs(a.a.Index(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AbsResult) Length() int {
|
||||||
|
return a.a.Length()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return series that having all the elements positive
|
||||||
|
func Abs(a Series) Series {
|
||||||
|
return &AbsResult{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Series = &AbsResult{}
|
||||||
|
|
||||||
|
func Predict(a Series, lookback int, offset ...int) float64 {
|
||||||
|
if a.Length() < lookback {
|
||||||
|
lookback = a.Length()
|
||||||
|
}
|
||||||
|
x := make([]float64, lookback, lookback)
|
||||||
|
y := make([]float64, lookback, lookback)
|
||||||
|
var weights []float64
|
||||||
|
for i := 0; i < lookback; i++ {
|
||||||
|
x[i] = float64(i)
|
||||||
|
y[i] = a.Index(i)
|
||||||
|
}
|
||||||
|
alpha, beta := stat.LinearRegression(x, y, weights, false)
|
||||||
|
o := -1.0
|
||||||
|
if len(offset) > 0 {
|
||||||
|
o = -float64(offset[0])
|
||||||
|
}
|
||||||
|
return alpha + beta*o
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will make prediction using Linear Regression to get the next cross point
|
||||||
|
// Return (offset from latest, crossed value, could cross)
|
||||||
|
// offset from latest should always be positive
|
||||||
|
// lookback param is to use at most `lookback` points to determine linear regression functions
|
||||||
|
//
|
||||||
|
// You may also refer to excel's FORECAST function
|
||||||
|
func NextCross(a Series, b Series, lookback int) (int, float64, bool) {
|
||||||
|
if a.Length() < lookback {
|
||||||
|
lookback = a.Length()
|
||||||
|
}
|
||||||
|
if b.Length() < lookback {
|
||||||
|
lookback = b.Length()
|
||||||
|
}
|
||||||
|
x := make([]float64, lookback, lookback)
|
||||||
|
y1 := make([]float64, lookback, lookback)
|
||||||
|
y2 := make([]float64, lookback, lookback)
|
||||||
|
var weights []float64
|
||||||
|
for i := 0; i < lookback; i++ {
|
||||||
|
x[i] = float64(i)
|
||||||
|
y1[i] = a.Index(i)
|
||||||
|
y2[i] = b.Index(i)
|
||||||
|
}
|
||||||
|
alpha1, beta1 := stat.LinearRegression(x, y1, weights, false)
|
||||||
|
alpha2, beta2 := stat.LinearRegression(x, y2, weights, false)
|
||||||
|
if beta2 == beta1 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
indexf := (alpha1 - alpha2) / (beta2 - beta1)
|
||||||
|
|
||||||
|
// crossed in different direction
|
||||||
|
if indexf >= 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return int(math.Ceil(-indexf)), alpha1 + beta1*indexf, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The result structure that maps to the crossing result of `CrossOver` and `CrossUnder`
|
||||||
|
// Accessible through BoolSeries interface
|
||||||
|
type CrossResult struct {
|
||||||
|
a Series
|
||||||
|
b Series
|
||||||
|
isOver bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CrossResult) Last() bool {
|
||||||
|
if c.Length() == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.isOver {
|
||||||
|
return c.a.Last()-c.b.Last() > 0 && c.a.Index(1)-c.b.Index(1) < 0
|
||||||
|
} else {
|
||||||
|
return c.a.Last()-c.b.Last() < 0 && c.a.Index(1)-c.b.Index(1) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CrossResult) Index(i int) bool {
|
||||||
|
if i >= c.Length() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.isOver {
|
||||||
|
return c.a.Index(i)-c.b.Index(i) > 0 && c.a.Index(i+1)-c.b.Index(i+1) < 0
|
||||||
|
} else {
|
||||||
|
return c.a.Index(i)-c.b.Index(i) < 0 && c.a.Index(i+1)-c.b.Index(i+1) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CrossResult) Length() int {
|
||||||
|
la := c.a.Length()
|
||||||
|
lb := c.b.Length()
|
||||||
|
if la > lb {
|
||||||
|
return lb
|
||||||
|
}
|
||||||
|
return la
|
||||||
|
}
|
||||||
|
|
||||||
|
// a series cross above b series.
|
||||||
|
// If in current KLine, a is higher than b, and in previous KLine, a is lower than b, then return true.
|
||||||
|
// Otherwise return false.
|
||||||
|
// If accessing index <= length, will always return false
|
||||||
|
func CrossOver(a Series, b Series) BoolSeries {
|
||||||
|
return &CrossResult{a, b, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a series cross under b series.
|
||||||
|
// If in current KLine, a is lower than b, and in previous KLine, a is higher than b, then return true.
|
||||||
|
// Otherwise return false.
|
||||||
|
// If accessing index <= length, will always return false
|
||||||
|
func CrossUnder(a Series, b Series) BoolSeries {
|
||||||
|
return &CrossResult{a, b, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Highest(a Series, lookback int) float64 {
|
||||||
|
if lookback > a.Length() {
|
||||||
|
lookback = a.Length()
|
||||||
|
}
|
||||||
|
highest := a.Last()
|
||||||
|
for i := 1; i < lookback; i++ {
|
||||||
|
current := a.Index(i)
|
||||||
|
if highest < current {
|
||||||
|
highest = current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return highest
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lowest(a Series, lookback int) float64 {
|
||||||
|
if lookback > a.Length() {
|
||||||
|
lookback = a.Length()
|
||||||
|
}
|
||||||
|
lowest := a.Last()
|
||||||
|
for i := 1; i < lookback; i++ {
|
||||||
|
current := a.Index(i)
|
||||||
|
if lowest > current {
|
||||||
|
lowest = current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lowest
|
||||||
|
}
|
||||||
|
|
||||||
|
type NumberSeries float64
|
||||||
|
|
||||||
|
func (a NumberSeries) Last() float64 {
|
||||||
|
return float64(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a NumberSeries) Index(_ int) float64 {
|
||||||
|
return float64(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a NumberSeries) Length() int {
|
||||||
|
return math.MaxInt32
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Series = NumberSeries(0)
|
||||||
|
|
||||||
|
type AddSeriesResult struct {
|
||||||
|
a Series
|
||||||
|
b Series
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add two series, result[i] = a[i] + b[i]
|
||||||
|
func Add(a interface{}, b interface{}) Series {
|
||||||
|
var aa Series
|
||||||
|
var bb Series
|
||||||
|
|
||||||
|
switch a.(type) {
|
||||||
|
case float64:
|
||||||
|
aa = NumberSeries(a.(float64))
|
||||||
|
case Series:
|
||||||
|
aa = a.(Series)
|
||||||
|
default:
|
||||||
|
panic("input should be either *Series or float64")
|
||||||
|
|
||||||
|
}
|
||||||
|
switch b.(type) {
|
||||||
|
case float64:
|
||||||
|
bb = NumberSeries(b.(float64))
|
||||||
|
case Series:
|
||||||
|
bb = b.(Series)
|
||||||
|
default:
|
||||||
|
panic("input should be either *Series or float64")
|
||||||
|
|
||||||
|
}
|
||||||
|
return &AddSeriesResult{aa, bb}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AddSeriesResult) Last() float64 {
|
||||||
|
return a.a.Last() + a.b.Last()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AddSeriesResult) Index(i int) float64 {
|
||||||
|
return a.a.Index(i) + a.b.Index(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AddSeriesResult) Length() int {
|
||||||
|
lengtha := a.a.Length()
|
||||||
|
lengthb := a.b.Length()
|
||||||
|
if lengtha < lengthb {
|
||||||
|
return lengtha
|
||||||
|
}
|
||||||
|
return lengthb
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Series = &AddSeriesResult{}
|
||||||
|
|
||||||
|
type MinusSeriesResult struct {
|
||||||
|
a Series
|
||||||
|
b Series
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minus two series, result[i] = a[i] - b[i]
|
||||||
|
func Minus(a interface{}, b interface{}) Series {
|
||||||
|
aa := switchIface(a)
|
||||||
|
bb := switchIface(b)
|
||||||
|
return &MinusSeriesResult{aa, bb}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MinusSeriesResult) Last() float64 {
|
||||||
|
return a.a.Last() - a.b.Last()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MinusSeriesResult) Index(i int) float64 {
|
||||||
|
return a.a.Index(i) - a.b.Index(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MinusSeriesResult) Length() int {
|
||||||
|
lengtha := a.a.Length()
|
||||||
|
lengthb := a.b.Length()
|
||||||
|
if lengtha < lengthb {
|
||||||
|
return lengtha
|
||||||
|
}
|
||||||
|
return lengthb
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Series = &MinusSeriesResult{}
|
||||||
|
|
||||||
|
func switchIface(b interface{}) Series {
|
||||||
|
switch b.(type) {
|
||||||
|
case float64:
|
||||||
|
return NumberSeries(b.(float64))
|
||||||
|
case int32:
|
||||||
|
return NumberSeries(float64(b.(int32)))
|
||||||
|
case int64:
|
||||||
|
return NumberSeries(float64(b.(int64)))
|
||||||
|
case float32:
|
||||||
|
return NumberSeries(float64(b.(float32)))
|
||||||
|
case int:
|
||||||
|
return NumberSeries(float64(b.(int)))
|
||||||
|
case Series:
|
||||||
|
return b.(Series)
|
||||||
|
default:
|
||||||
|
fmt.Println(reflect.TypeOf(b))
|
||||||
|
panic("input should be either *Series or float64")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divid two series, result[i] = a[i] / b[i]
|
||||||
|
func Div(a interface{}, b interface{}) Series {
|
||||||
|
aa := switchIface(a)
|
||||||
|
if 0 == b {
|
||||||
|
panic("Divid by zero exception")
|
||||||
|
}
|
||||||
|
bb := switchIface(b)
|
||||||
|
return &DivSeriesResult{aa, bb}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type DivSeriesResult struct {
|
||||||
|
a Series
|
||||||
|
b Series
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *DivSeriesResult) Last() float64 {
|
||||||
|
return a.a.Last() / a.b.Last()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *DivSeriesResult) Index(i int) float64 {
|
||||||
|
return a.a.Index(i) / a.b.Index(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *DivSeriesResult) Length() int {
|
||||||
|
lengtha := a.a.Length()
|
||||||
|
lengthb := a.b.Length()
|
||||||
|
if lengtha < lengthb {
|
||||||
|
return lengtha
|
||||||
|
}
|
||||||
|
return lengthb
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Series = &DivSeriesResult{}
|
||||||
|
|
||||||
|
// Multiple two series, result[i] = a[i] * b[i]
|
||||||
|
func Mul(a interface{}, b interface{}) Series {
|
||||||
|
var aa Series
|
||||||
|
var bb Series
|
||||||
|
|
||||||
|
switch a.(type) {
|
||||||
|
case float64:
|
||||||
|
aa = NumberSeries(a.(float64))
|
||||||
|
case Series:
|
||||||
|
aa = a.(Series)
|
||||||
|
default:
|
||||||
|
panic("input should be either Series or float64")
|
||||||
|
}
|
||||||
|
switch b.(type) {
|
||||||
|
case float64:
|
||||||
|
bb = NumberSeries(b.(float64))
|
||||||
|
case Series:
|
||||||
|
bb = b.(Series)
|
||||||
|
default:
|
||||||
|
panic("input should be either Series or float64")
|
||||||
|
|
||||||
|
}
|
||||||
|
return &MulSeriesResult{aa, bb}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type MulSeriesResult struct {
|
||||||
|
a Series
|
||||||
|
b Series
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MulSeriesResult) Last() float64 {
|
||||||
|
return a.a.Last() * a.b.Last()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MulSeriesResult) Index(i int) float64 {
|
||||||
|
return a.a.Index(i) * a.b.Index(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *MulSeriesResult) Length() int {
|
||||||
|
lengtha := a.a.Length()
|
||||||
|
lengthb := a.b.Length()
|
||||||
|
if lengtha < lengthb {
|
||||||
|
return lengtha
|
||||||
|
}
|
||||||
|
return lengthb
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Series = &MulSeriesResult{}
|
||||||
|
|
||||||
|
// Calculate (a dot b).
|
||||||
|
// if limit is given, will only calculate the first limit numbers (a.Index[0..limit])
|
||||||
|
// otherwise will operate on all elements
|
||||||
|
func Dot(a interface{}, b interface{}, limit ...int) float64 {
|
||||||
|
return Sum(Mul(a, b), limit...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract elements from the Series to a float64 array, following the order of Index(0..limit)
|
||||||
|
// if limit is given, will only take the first limit numbers (a.Index[0..limit])
|
||||||
|
// otherwise will operate on all elements
|
||||||
|
func ToArray(a Series, limit ...int) (result []float64) {
|
||||||
|
l := -1
|
||||||
|
if len(limit) > 0 {
|
||||||
|
l = limit[0]
|
||||||
|
}
|
||||||
|
if l < a.Length() {
|
||||||
|
l = a.Length()
|
||||||
|
}
|
||||||
|
result = make([]float64, l, l)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
result[i] = a.Index(i)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar to ToArray but in reverse order.
|
||||||
|
// Useful when you want to cache series' calculated result as float64 array
|
||||||
|
// the then reuse the result in multiple places (so that no recalculation will be triggered)
|
||||||
|
//
|
||||||
|
// notice that the return type is a Float64Slice, which implements the Series interface
|
||||||
|
func ToReverseArray(a Series, limit ...int) (result Float64Slice) {
|
||||||
|
l := -1
|
||||||
|
if len(limit) > 0 {
|
||||||
|
l = limit[0]
|
||||||
|
}
|
||||||
|
if l < a.Length() {
|
||||||
|
l = a.Length()
|
||||||
|
}
|
||||||
|
result = make([]float64, l, l)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
result[l-i-1] = a.Index(i)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeResult struct {
|
||||||
|
a Series
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChangeResult) Last() float64 {
|
||||||
|
if c.offset >= c.a.Length() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return c.a.Last() - c.a.Index(c.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChangeResult) Index(i int) float64 {
|
||||||
|
if i+c.offset >= c.a.Length() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return c.a.Index(i) - c.a.Index(i+c.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChangeResult) Length() int {
|
||||||
|
length := c.a.Length()
|
||||||
|
if length >= c.offset {
|
||||||
|
return length - c.offset
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference between current value and previous, a - a[offset]
|
||||||
|
// offset: if not given, offset is 1.
|
||||||
|
func Change(a Series, offset ...int) Series {
|
||||||
|
o := 1
|
||||||
|
if len(offset) == 0 {
|
||||||
|
o = offset[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ChangeResult{a, o}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: ta.linreg
|
||||||
|
|
35
pkg/types/indicator_test.go
Normal file
35
pkg/types/indicator_test.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFloat(t *testing.T) {
|
||||||
|
var a Series = Minus(3., 2.)
|
||||||
|
assert.Equal(t, a.Last(), 1.)
|
||||||
|
assert.Equal(t, a.Index(100), 1.)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextCross(t *testing.T) {
|
||||||
|
var a Series = NumberSeries(1.2)
|
||||||
|
|
||||||
|
var b Series = &Float64Slice{100., 80., 60.}
|
||||||
|
// index 2 1 0
|
||||||
|
// predicted 40 20 0
|
||||||
|
// offset 1 2 3
|
||||||
|
|
||||||
|
index, value, ok := NextCross(a, b, 3)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, value, 1.2)
|
||||||
|
assert.Equal(t, index, 3) // 2.94, ceil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64Slice(t *testing.T) {
|
||||||
|
var a = Float64Slice{1.0, 2.0, 3.0}
|
||||||
|
var b = Float64Slice{1.0, 2.0, 3.0}
|
||||||
|
var c Series = Minus(&a, &b)
|
||||||
|
a = append(a, 4.0)
|
||||||
|
b = append(b, 3.0)
|
||||||
|
assert.Equal(t, c.Last(), 1.)
|
||||||
|
}
|
|
@ -508,3 +508,97 @@ func (k KLineWindow) SlackAttachment() slack.Attachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
type KLineCallback func(kline KLine)
|
type KLineCallback func(kline KLine)
|
||||||
|
|
||||||
|
type KValueType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
kOpUnknown KValueType = iota
|
||||||
|
kOpenValue
|
||||||
|
kCloseValue
|
||||||
|
kHighValue
|
||||||
|
kLowValue
|
||||||
|
kVolumeValue
|
||||||
|
)
|
||||||
|
|
||||||
|
func (k *KLineWindow) High() Series {
|
||||||
|
return &KLineSeries{
|
||||||
|
lines: k,
|
||||||
|
kv: kHighValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KLineWindow) Low() Series {
|
||||||
|
return &KLineSeries{
|
||||||
|
lines: k,
|
||||||
|
kv: kLowValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KLineWindow) Open() Series {
|
||||||
|
return &KLineSeries{
|
||||||
|
lines: k,
|
||||||
|
kv: kOpenValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KLineWindow) Close() Series {
|
||||||
|
return &KLineSeries{
|
||||||
|
lines: k,
|
||||||
|
kv: kCloseValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KLineWindow) Volume() Series {
|
||||||
|
return &KLineSeries{
|
||||||
|
lines: k,
|
||||||
|
kv: kVolumeValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type KLineSeries struct {
|
||||||
|
lines *KLineWindow
|
||||||
|
kv KValueType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KLineSeries) Last() float64 {
|
||||||
|
length := len(*k.lines)
|
||||||
|
switch k.kv {
|
||||||
|
case kOpenValue:
|
||||||
|
return (*k.lines)[length-1].GetOpen().Float64()
|
||||||
|
case kCloseValue:
|
||||||
|
return (*k.lines)[length-1].GetClose().Float64()
|
||||||
|
case kLowValue:
|
||||||
|
return (*k.lines)[length-1].GetLow().Float64()
|
||||||
|
case kHighValue:
|
||||||
|
return (*k.lines)[length-1].GetHigh().Float64()
|
||||||
|
case kVolumeValue:
|
||||||
|
return (*k.lines)[length-1].Volume.Float64()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KLineSeries) Index(i int) float64 {
|
||||||
|
length := len(*k.lines)
|
||||||
|
if length == 0 || length-i-1 < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
switch k.kv {
|
||||||
|
case kOpenValue:
|
||||||
|
return (*k.lines)[length-i-1].GetOpen().Float64()
|
||||||
|
case kCloseValue:
|
||||||
|
return (*k.lines)[length-i-1].GetClose().Float64()
|
||||||
|
case kLowValue:
|
||||||
|
return (*k.lines)[length-i-1].GetLow().Float64()
|
||||||
|
case kHighValue:
|
||||||
|
return (*k.lines)[length-i-1].GetHigh().Float64()
|
||||||
|
case kVolumeValue:
|
||||||
|
return (*k.lines)[length-i-1].Volume.Float64()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KLineSeries) Length() int {
|
||||||
|
return len(*k.lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Series = &KLineSeries{}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user