From 9eaf69388c7d959b7840f99e40adf303cea31e14 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 7 Dec 2020 23:03:06 +0800 Subject: [PATCH] add fixedpoint json marshaling --- pkg/bbgo/redis_persistence.go | 43 +++++++++++++++---- pkg/bbgo/redis_persistence_test.go | 69 ++++++++++++++++++++++++++++++ pkg/fixedpoint/convert.go | 6 +++ pkg/indicator/ewma.go | 4 +- pkg/indicator/ewma_test.go | 32 +++++++++++--- 5 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 pkg/bbgo/redis_persistence_test.go diff --git a/pkg/bbgo/redis_persistence.go b/pkg/bbgo/redis_persistence.go index 5be8f559c8..dbbf83922f 100644 --- a/pkg/bbgo/redis_persistence.go +++ b/pkg/bbgo/redis_persistence.go @@ -20,6 +20,7 @@ type PersistenceService interface { type Store interface { Load(val interface{}) error Save(val interface{}) error + Reset() error } type MemoryService struct { @@ -54,10 +55,17 @@ func (store *MemoryStore) Load(val interface{}) error { v := reflect.ValueOf(val) if data, ok := store.memory.Slots[store.Key]; ok { v.Elem().Set(reflect.ValueOf(data).Elem()) + } else { + return os.ErrNotExist } return nil } +func (store *MemoryStore) Reset() error { + delete(store.memory.Slots, store.Key) + return nil +} + type JsonPersistenceService struct { Directory string } @@ -74,6 +82,19 @@ type JsonStore struct { Directory string } +func (store JsonStore) Reset() error { + if _, err := os.Stat(store.Directory); os.IsNotExist(err) { + return nil + } + + p := filepath.Join(store.Directory, store.ID) + ".json" + if _, err := os.Stat(p); os.IsNotExist(err) { + return nil + } + + return os.Remove(p) +} + func (store JsonStore) Load(val interface{}) error { if _, err := os.Stat(store.Directory); os.IsNotExist(err) { if err2 := os.Mkdir(store.Directory, 0777); err2 != nil { @@ -82,13 +103,18 @@ func (store JsonStore) Load(val interface{}) error { } p := filepath.Join(store.Directory, store.ID) + ".json" + + if _, err := os.Stat(p); os.IsNotExist(err) { + return os.ErrNotExist + } + data, err := ioutil.ReadFile(p) if err != nil { return err } if len(data) == 0 { - return nil + return os.ErrNotExist } return json.Unmarshal(data, val) @@ -149,21 +175,17 @@ func (store *RedisStore) Load(val interface{}) error { data, err := cmd.Result() if err != nil { if err == redis.Nil { - return nil + return os.ErrNotExist } return err } if len(data) == 0 { - return nil - } - - if err := json.Unmarshal([]byte(data), val); err != nil { - return err + return os.ErrNotExist } - return nil + return json.Unmarshal([]byte(data), val) } func (store *RedisStore) Save(val interface{}) error { @@ -176,3 +198,8 @@ func (store *RedisStore) Save(val interface{}) error { _, err = cmd.Result() return err } + +func (store *RedisStore) Reset() error { + _, err := store.redis.Del(context.Background(), store.ID).Result() + return err +} diff --git a/pkg/bbgo/redis_persistence_test.go b/pkg/bbgo/redis_persistence_test.go new file mode 100644 index 0000000000..238687a380 --- /dev/null +++ b/pkg/bbgo/redis_persistence_test.go @@ -0,0 +1,69 @@ +package bbgo + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/fixedpoint" +) + +func TestRedisPersistentService(t *testing.T) { + redisService := NewRedisPersistenceService(&RedisPersistenceConfig{ + Host: "127.0.0.1", + Port: "6379", + DB: 0, + }) + assert.NotNil(t, redisService) + + store := redisService.NewStore("bbgo", "test") + assert.NotNil(t, store) + + err := store.Reset() + assert.NoError(t, err) + + var fp fixedpoint.Value + err = store.Load(fp) + assert.Error(t, err) + assert.EqualError(t, os.ErrNotExist, err.Error()) + + fp = fixedpoint.NewFromFloat(3.1415) + err = store.Save(&fp) + assert.NoError(t, err, "should store value without error") + + var fp2 fixedpoint.Value + err = store.Load(&fp2) + assert.NoError(t, err, "should load value without error") + assert.Equal(t, fp, fp2) + + err = store.Reset() + assert.NoError(t, err) +} + +func TestMemoryService(t *testing.T) { + t.Run("load_empty", func(t *testing.T) { + service := NewMemoryService() + store := service.NewStore("test") + + j := 0 + err := store.Load(&j) + assert.Error(t, err) + }) + + t.Run("save_and_load", func(t *testing.T) { + service := NewMemoryService() + store := service.NewStore("test") + + i := 3 + err := store.Save(&i) + + assert.NoError(t, err) + + var j = 0 + err = store.Load(&j) + assert.NoError(t, err) + assert.Equal(t, i, j) + }) +} + diff --git a/pkg/fixedpoint/convert.go b/pkg/fixedpoint/convert.go index 95d6d0e568..1485fda00b 100644 --- a/pkg/fixedpoint/convert.go +++ b/pkg/fixedpoint/convert.go @@ -76,6 +76,12 @@ func (v *Value) UnmarshalYAML(unmarshal func(a interface{}) error) (err error) { return err } +func (v Value) MarshalJSON() ([]byte, error) { + f := float64(v) / DefaultPow + o := fmt.Sprintf("%f", f) + return []byte(o), nil +} + func (v *Value) UnmarshalJSON(data []byte) error { var a interface{} var err = json.Unmarshal(data, &a) diff --git a/pkg/indicator/ewma.go b/pkg/indicator/ewma.go index 9c8ddab150..0690fdbffc 100644 --- a/pkg/indicator/ewma.go +++ b/pkg/indicator/ewma.go @@ -64,13 +64,13 @@ func (inc *EWMA) calculateAndUpdate(allKLines []types.KLine) { } v1 := math.Floor(inc.Values[len(inc.Values)-1]*100.0) / 100.0 - v2 := math.Floor(CalculateKLineEWMA(allKLines, priceF, inc.Window)*100.0) / 100.0 + v2 := math.Floor(CalculateKLinesEMA(allKLines, priceF, inc.Window)*100.0) / 100.0 if v1 != v2 { log.Warnf("ACCUMULATED %s EMA (%d) %f != EMA %f", inc.Interval, inc.Window, v1, v2) } } -func CalculateKLineEWMA(allKLines []types.KLine, priceF KLinePriceMapper, window int) float64 { +func CalculateKLinesEMA(allKLines []types.KLine, priceF KLinePriceMapper, window int) float64 { var multiplier = 2.0 / (float64(window) + 1) return ewma(MapKLinePrice(allKLines, priceF), multiplier) } diff --git a/pkg/indicator/ewma_test.go b/pkg/indicator/ewma_test.go index 858148b488..d752b5f0c6 100644 --- a/pkg/indicator/ewma_test.go +++ b/pkg/indicator/ewma_test.go @@ -1025,7 +1025,7 @@ func buildKLines(prices []float64) (klines []types.KLine) { func Test_calculateEWMA(t *testing.T) { type args struct { allKLines []types.KLine - priceF KLinePriceMapper + priceF KLinePriceMapper window int } tests := []struct { @@ -1034,21 +1034,39 @@ func Test_calculateEWMA(t *testing.T) { want float64 }{ { - name: "ethusdt ewma 7", + name: "ETHUSDT EMA 7", args: args{ allKLines: buildKLines(ethusdt5m), - priceF: KLineClosePriceMapper, + priceF: KLineClosePriceMapper, window: 7, }, - want: 571.72, // with open price, binance disktop returns 571.45, trading view returns 570.8957, for close price, binance mobile returns 571.72 + want: 571.72, // with open price, binance desktop returns 571.45, trading view returns 570.8957, for close price, binance mobile returns 571.72 + }, + { + name: "ETHUSDT EMA 25", + args: args{ + allKLines: buildKLines(ethusdt5m), + priceF: KLineClosePriceMapper, + window: 25, + }, + want: 571.30, + }, + { + name: "ETHUSDT EMA 99", + args: args{ + allKLines: buildKLines(ethusdt5m), + priceF: KLineClosePriceMapper, + window: 99, + }, + want: 577.62, // binance mobile uses 577.58 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := CalculateKLineEWMA(tt.args.allKLines, tt.args.priceF, tt.args.window) - got = math.Trunc(got * 100.0) / 100.0 + got := CalculateKLinesEMA(tt.args.allKLines, tt.args.priceF, tt.args.window) + got = math.Trunc(got*100.0) / 100.0 if got != tt.want { - t.Errorf("CalculateKLineEWMA() = %v, want %v", got, tt.want) + t.Errorf("CalculateKLinesEMA() = %v, want %v", got, tt.want) } }) }