mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
Merge pull request #1229 from c9s/c9s/indicator-cci-v2
This commit is contained in:
commit
c54031b0e8
|
@ -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
|
||||||
|
|
|
@ -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
65
pkg/indicator/v2/cci.go
Normal 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
|
||||||
|
}
|
39
pkg/indicator/v2/cci_test.go
Normal file
39
pkg/indicator/v2/cci_test.go
Normal 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")
|
||||||
|
})
|
||||||
|
}
|
5
pkg/indicator/v2/const.go
Normal file
5
pkg/indicator/v2/const.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package indicatorv2
|
||||||
|
|
||||||
|
import "github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
|
||||||
|
var three = fixedpoint.NewFromInt(3)
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -3,24 +3,20 @@
|
||||||
package tri
|
package tri
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/cache"
|
|
||||||
"github.com/c9s/bbgo/pkg/exchange/binance"
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var markets = make(types.MarketMap)
|
var markets = make(types.MarketMap)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
if err := util.ReadJsonFile("../../../testdata/binance-markets.json", &markets); err != nil {
|
||||||
markets, err = cache.LoadExchangeMarketsWithCache(context.Background(), &binance.Exchange{})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -2,7 +2,9 @@ package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WriteJsonFile(p string, obj interface{}) error {
|
func WriteJsonFile(p string, obj interface{}) error {
|
||||||
|
@ -13,3 +15,24 @@ func WriteJsonFile(p string, obj interface{}) error {
|
||||||
|
|
||||||
return ioutil.WriteFile(p, out, 0644)
|
return ioutil.WriteFile(p, out, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReadJsonFile(file string, obj interface{}) error {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
byteResult, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(byteResult), obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
82
testdata/binance-markets.json
vendored
Normal file
82
testdata/binance-markets.json
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
{
|
||||||
|
"ETHUSDT": {
|
||||||
|
"symbol": "ETHUSDT",
|
||||||
|
"localSymbol": "ETHUSDT",
|
||||||
|
"pricePrecision": 8,
|
||||||
|
"volumePrecision": 8,
|
||||||
|
"quoteCurrency": "USDT",
|
||||||
|
"baseCurrency": "ETH",
|
||||||
|
"minNotional": 10,
|
||||||
|
"minAmount": 10,
|
||||||
|
"minQuantity": 0.0001,
|
||||||
|
"maxQuantity": 9000,
|
||||||
|
"stepSize": 0.0001,
|
||||||
|
"minPrice": 0.01,
|
||||||
|
"maxPrice": 1000000,
|
||||||
|
"tickSize": 0.01
|
||||||
|
},
|
||||||
|
"BTCUSDT": {
|
||||||
|
"symbol": "BTCUSDT",
|
||||||
|
"localSymbol": "BTCUSDT",
|
||||||
|
"pricePrecision": 8,
|
||||||
|
"volumePrecision": 8,
|
||||||
|
"quoteCurrency": "USDT",
|
||||||
|
"baseCurrency": "BTC",
|
||||||
|
"minNotional": 10,
|
||||||
|
"minAmount": 10,
|
||||||
|
"minQuantity": 1e-05,
|
||||||
|
"maxQuantity": 9000,
|
||||||
|
"stepSize": 1e-05,
|
||||||
|
"minPrice": 0.01,
|
||||||
|
"maxPrice": 1000000,
|
||||||
|
"tickSize": 0.01
|
||||||
|
},
|
||||||
|
"LINKUSDT": {
|
||||||
|
"symbol": "LINKUSDT",
|
||||||
|
"localSymbol": "LINKUSDT",
|
||||||
|
"pricePrecision": 8,
|
||||||
|
"volumePrecision": 8,
|
||||||
|
"quoteCurrency": "USDT",
|
||||||
|
"baseCurrency": "LINK",
|
||||||
|
"minNotional": 5,
|
||||||
|
"minAmount": 5,
|
||||||
|
"minQuantity": 0.01,
|
||||||
|
"maxQuantity": 90000,
|
||||||
|
"stepSize": 0.01,
|
||||||
|
"minPrice": 0.001,
|
||||||
|
"maxPrice": 10000,
|
||||||
|
"tickSize": 0.001
|
||||||
|
},
|
||||||
|
"LTCUSDT": {
|
||||||
|
"symbol": "LTCUSDT",
|
||||||
|
"localSymbol": "LTCUSDT",
|
||||||
|
"pricePrecision": 8,
|
||||||
|
"volumePrecision": 8,
|
||||||
|
"quoteCurrency": "USDT",
|
||||||
|
"baseCurrency": "LTC",
|
||||||
|
"minNotional": 5,
|
||||||
|
"minAmount": 5,
|
||||||
|
"minQuantity": 0.001,
|
||||||
|
"maxQuantity": 90000,
|
||||||
|
"stepSize": 0.001,
|
||||||
|
"minPrice": 0.01,
|
||||||
|
"maxPrice": 100000,
|
||||||
|
"tickSize": 0.01
|
||||||
|
},
|
||||||
|
"ETHBTC": {
|
||||||
|
"symbol": "ETHBTC",
|
||||||
|
"localSymbol": "ETHBTC",
|
||||||
|
"pricePrecision": 8,
|
||||||
|
"volumePrecision": 8,
|
||||||
|
"quoteCurrency": "BTC",
|
||||||
|
"baseCurrency": "ETH",
|
||||||
|
"minNotional": 0.0001,
|
||||||
|
"minAmount": 0.0001,
|
||||||
|
"minQuantity": 0.0001,
|
||||||
|
"maxQuantity": 100000,
|
||||||
|
"stepSize": 0.0001,
|
||||||
|
"minPrice": 1e-05,
|
||||||
|
"maxPrice": 922327,
|
||||||
|
"tickSize": 1e-05
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user