rewrite cci indicator in v2 indicator

This commit is contained in:
c9s 2023-07-11 10:13:13 +08:00
parent 4648b5434e
commit ce481ba52d
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
9 changed files with 140 additions and 9 deletions

View File

@ -11,6 +11,12 @@ import (
// Refer URL: http://www.andrewshamlet.net/2017/07/08/python-tutorial-cci // Refer URL: http://www.andrewshamlet.net/2017/07/08/python-tutorial-cci
// with modification of ddof=0 to let standard deviation to be divided by N instead of N-1 // with modification of ddof=0 to let standard deviation to be divided by N instead of N-1
// //
// CCI = (Typical Price - n-period SMA of TP) / (Constant x Mean Deviation)
//
// Typical Price (TP) = (High + Low + Close)/3
//
// Constant = .015
//
// The Commodity Channel Index (CCI) is a technical analysis indicator that is used to identify potential overbought or oversold conditions // The Commodity Channel Index (CCI) is a technical analysis indicator that is used to identify potential overbought or oversold conditions
// in a security's price. It was originally developed for use in commodity markets, but can be applied to any security that has a sufficient // in a security's price. It was originally developed for use in commodity markets, but can be applied to any security that has a sufficient
// amount of price data. The CCI is calculated by taking the difference between the security's typical price (the average of its high, low, and // amount of price data. The CCI is calculated by taking the difference between the security's typical price (the average of its high, low, and
@ -43,16 +49,18 @@ func (inc *CCI) Update(value float64) {
} }
inc.Input.Push(value) inc.Input.Push(value)
tp := inc.TypicalPrice.Last(0) - inc.Input.Index(inc.Window) + value tp := inc.TypicalPrice.Last(0) - inc.Input.Last(inc.Window) + value
inc.TypicalPrice.Push(tp) inc.TypicalPrice.Push(tp)
if len(inc.Input) < inc.Window { if len(inc.Input) < inc.Window {
return return
} }
ma := tp / float64(inc.Window) ma := tp / float64(inc.Window)
inc.MA.Push(ma) inc.MA.Push(ma)
if len(inc.MA) > MaxNumOfEWMA { if len(inc.MA) > MaxNumOfEWMA {
inc.MA = inc.MA[MaxNumOfEWMATruncateSize-1:] inc.MA = inc.MA[MaxNumOfEWMATruncateSize-1:]
} }
md := 0. md := 0.
for i := 0; i < inc.Window; i++ { for i := 0; i < inc.Window; i++ {
diff := inc.Input.Last(i) - ma diff := inc.Input.Last(i) - ma

View File

@ -4,8 +4,9 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/c9s/bbgo/pkg/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/types"
) )
/* /*
@ -19,7 +20,7 @@ print(cci)
func Test_CCI(t *testing.T) { func Test_CCI(t *testing.T) {
var randomPrices = []byte(`[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`) var randomPrices = []byte(`[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`)
var input []float64 var input []float64
var Delta = 4.3e-2 var delta = 4.3e-2
if err := json.Unmarshal(randomPrices, &input); err != nil { if err := json.Unmarshal(randomPrices, &input); err != nil {
panic(err) panic(err)
} }
@ -30,8 +31,8 @@ func Test_CCI(t *testing.T) {
} }
last := cci.Last(0) last := cci.Last(0)
assert.InDelta(t, 93.250481, last, Delta) assert.InDelta(t, 93.250481, last, delta)
assert.InDelta(t, 81.813449, cci.Index(1), Delta) assert.InDelta(t, 81.813449, cci.Index(1), delta)
assert.Equal(t, 50-16+1, cci.Length()) assert.Equal(t, 50-16+1, cci.Length())
}) })
} }

65
pkg/indicator/v2/cci.go Normal file
View File

@ -0,0 +1,65 @@
package indicatorv2
import (
"math"
"github.com/c9s/bbgo/pkg/types"
)
// Refer: Commodity Channel Index
// Refer URL: http://www.andrewshamlet.net/2017/07/08/python-tutorial-cci
// with modification of ddof=0 to let standard deviation to be divided by N instead of N-1
//
// CCI = (Typical Price - n-period SMA of TP) / (Constant x Mean Deviation)
//
// Typical Price (TP) = (High + Low + Close)/3
//
// Constant = .015
//
// The Commodity Channel Index (CCI) is a technical analysis indicator that is used to identify potential overbought or oversold conditions
// in a security's price. It was originally developed for use in commodity markets, but can be applied to any security that has a sufficient
// amount of price data. The CCI is calculated by taking the difference between the security's typical price (the average of its high, low, and
// closing prices) and its moving average, and then dividing the result by the mean absolute deviation of the typical price. This resulting value
// is then plotted as a line on the price chart, with values above +100 indicating overbought conditions and values below -100 indicating
// oversold conditions. The CCI can be used by traders to identify potential entry and exit points for trades, or to confirm other technical
// analysis signals.
type CCIStream struct {
*types.Float64Series
TypicalPrice *types.Float64Series
source types.Float64Source
window int
}
func CCI(source types.Float64Source, window int) *CCIStream {
s := &CCIStream{
Float64Series: types.NewFloat64Series(),
TypicalPrice: types.NewFloat64Series(),
source: source,
window: window,
}
s.Bind(source, s)
return s
}
func (s *CCIStream) Calculate(value float64) float64 {
var tp = value
if s.TypicalPrice.Length() > 0 {
tp = s.TypicalPrice.Last(0) - s.source.Last(s.window) + value
}
s.TypicalPrice.Push(tp)
ma := tp / float64(s.window)
md := 0.
for i := 0; i < s.window; i++ {
diff := s.source.Last(i) - ma
md += diff * diff
}
md = math.Sqrt(md / float64(s.window))
cci := (value - ma) / (0.015 * md)
return cci
}

View File

@ -0,0 +1,39 @@
package indicatorv2
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
/*
python:
import pandas as pd
s = pd.Series([0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9])
cci = pd.Series((s - s.rolling(16).mean()) / (0.015 * s.rolling(16).std(ddof=0)), name="CCI")
print(cci)
*/
func Test_CCI(t *testing.T) {
var randomPrices = []byte(`[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`)
var input []float64
var delta = 4.3e-2
if err := json.Unmarshal(randomPrices, &input); err != nil {
panic(err)
}
t.Run("random_case", func(t *testing.T) {
price := Price(nil, nil)
cci := CCI(price, 16)
for _, value := range input {
price.PushAndEmit(value)
}
t.Logf("cci: %+v", cci.Slice)
last := cci.Last(0)
assert.InDelta(t, 93.250481, last, delta)
assert.InDelta(t, 81.813449, cci.Index(1), delta)
assert.Equal(t, 50, cci.Length(), "length")
})
}

View File

@ -0,0 +1,5 @@
package indicatorv2
import "github.com/c9s/bbgo/pkg/fixedpoint"
var three = fixedpoint.NewFromInt(3)

View File

@ -65,3 +65,7 @@ func OpenPrices(source KLineSubscription) *PriceStream {
func Volumes(source KLineSubscription) *PriceStream { func Volumes(source KLineSubscription) *PriceStream {
return Price(source, types.KLineVolumeMapper) return Price(source, types.KLineVolumeMapper)
} }
func HLC3(source KLineSubscription) *PriceStream {
return Price(source, types.KLineHLC3Mapper)
}

View File

@ -47,8 +47,8 @@ func Stoch(source KLineSubscription, window, dPeriod int) *StochStream {
lowest := s.lowPrices.Slice.Tail(s.window).Min() lowest := s.lowPrices.Slice.Tail(s.window).Min()
highest := s.highPrices.Slice.Tail(s.window).Max() highest := s.highPrices.Slice.Tail(s.window).Max()
var k float64 = 50.0 var k = 50.0
var d float64 = 0.0 var d = 0.0
if highest != lowest { if highest != lowest {
k = 100.0 * (kLine.Close.Float64() - lowest) / (highest - lowest) k = 100.0 * (kLine.Close.Float64() - lowest) / (highest - lowest)

View File

@ -10,14 +10,15 @@ import (
"github.com/c9s/bbgo/pkg/style" "github.com/c9s/bbgo/pkg/style"
) )
var Two = fixedpoint.NewFromInt(2)
var Three = fixedpoint.NewFromInt(3)
type Direction int type Direction int
const DirectionUp = 1 const DirectionUp = 1
const DirectionNone = 0 const DirectionNone = 0
const DirectionDown = -1 const DirectionDown = -1
var Two = fixedpoint.NewFromInt(2)
type KLineOrWindow interface { type KLineOrWindow interface {
GetInterval() string GetInterval() string
Direction() Direction Direction() Direction
@ -668,6 +669,10 @@ func KLineVolumeMapper(k KLine) float64 {
return k.Volume.Float64() return k.Volume.Float64()
} }
func KLineHLC3Mapper(k KLine) float64 {
return k.High.Add(k.Low).Add(k.Close).Div(Three).Float64()
}
func MapKLinePrice(kLines []KLine, f KLineValueMapper) (prices []float64) { func MapKLinePrice(kLines []KLine, f KLineValueMapper) (prices []float64) {
for _, k := range kLines { for _, k := range kLines {
prices = append(prices, f(k)) prices = append(prices, f(k))

View File

@ -29,6 +29,10 @@ func (f *Float64Series) Length() int {
return len(f.Slice) return len(f.Slice)
} }
func (f *Float64Series) Push(x float64) {
f.Slice.Push(x)
}
func (f *Float64Series) PushAndEmit(x float64) { func (f *Float64Series) PushAndEmit(x float64) {
f.Slice.Push(x) f.Slice.Push(x)
f.EmitUpdate(x) f.EmitUpdate(x)