feature: ewo add draw function

This commit is contained in:
zenix 2022-09-02 18:00:16 +09:00
parent 81084ddea6
commit ff7fd38372
3 changed files with 127 additions and 24 deletions

View File

@ -29,21 +29,26 @@ exchangeStrategies:
stoploss: 0.15%
windowATR: 14
windowQuick: 3
windowSlow: 19
windowSlow: 13
source: hl2
pendingMinutes: 8
drawGraph: true
graphIndicatorPath: "./indicator.png"
graphPNLPath: "./pnl.png"
graphCumPNLPath: "./cumpnl.png"
# ActivationRatio should be increasing order
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
#trailingActivationRatio: [0.01, 0.016, 0.05]
#trailingActivationRatio: [0.001, 0.0081, 0.022]
trailingActivationRatio: [0.0006, 0.0008, 0.0012, 0.0017, 0.01]
trailingActivationRatio: [0.0008, 0.0012, 0.0017, 0.01, 0.015]
#trailingActivationRatio: []
#trailingCallbackRate: []
#trailingCallbackRate: [0.002, 0.01, 0.1]
#trailingCallbackRate: [0.0004, 0.0009, 0.018]
trailingCallbackRate: [0.0001, 0.0002, 0.0003, 0.0006, 0.0049]
trailingCallbackRate: [0.0002, 0.0003, 0.0006, 0.0049, 0.006]
#exits:
# - roiStopLoss:

View File

@ -1,10 +1,53 @@
package elliottwave
import (
"bytes"
"fmt"
"os"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/interact"
"github.com/c9s/bbgo/pkg/types"
"github.com/wcharczuk/go-chart/v2"
)
func (s *Strategy) InitDrawCommands(store *bbgo.SerialMarketDataStore, profit, cumProfit types.Series) {
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
canvas := s.DrawIndicators(store)
if canvas == nil {
reply.Message("cannot render indicators")
return
}
var buffer bytes.Buffer
if err := canvas.Render(chart.PNG, &buffer); err != nil {
log.WithError(err).Errorf("cannot render indicators in ewo")
reply.Message(fmt.Sprintf("[error] cannot render indicators in ewo: %v", err))
return
}
bbgo.SendPhoto(&buffer)
})
bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) {
canvas := s.DrawPNL(profit)
var buffer bytes.Buffer
if err := canvas.Render(chart.PNG, &buffer); err != nil {
log.WithError(err).Errorf("cannot render pnl in drift")
reply.Message(fmt.Sprintf("[error] cannot render pnl in ewo: %v", err))
return
}
bbgo.SendPhoto(&buffer)
})
bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) {
canvas := s.DrawCumPNL(cumProfit)
var buffer bytes.Buffer
if err := canvas.Render(chart.PNG, &buffer); err != nil {
log.WithError(err).Errorf("cannot render cumpnl in drift")
reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err))
return
}
bbgo.SendPhoto(&buffer)
})
}
func (s *Strategy) DrawIndicators(store *bbgo.SerialMarketDataStore) *types.Canvas {
klines, ok := store.KLinesOfInterval(types.Interval1m)
if !ok {
@ -18,18 +61,78 @@ func (s *Strategy) DrawIndicators(store *bbgo.SerialMarketDataStore) *types.Canv
}
log.Infof("draw indicators with %d data", Length)
mean := s.priceLines.Mean(Length)
high := s.priceLines.Highest(Length)
low := s.priceLines.Lowest(Length)
ehigh := types.Highest(s.ewo, Length)
elow := types.Lowest(s.ewo, Length)
canvas.Plot("ewo", types.Add(types.Mul(s.ewo, (high-low)/(ehigh-elow)), mean), time, Length)
canvas.Plot("zero", types.NumberSeries(mean), time, Length)
canvas.Plot("price", s.priceLines, time, Length)
return canvas
}
func (s *Strategy) DrawPNL(profit types.Series) *types.Canvas {
return nil
canvas := types.NewCanvas(s.InstanceID())
length := profit.Length()
log.Errorf("pnl Highest: %f, Lowest: %f", types.Highest(profit, length), types.Lowest(profit, length))
canvas.PlotRaw("pnl %", profit, length)
canvas.YAxis = chart.YAxis{
ValueFormatter: func(v interface{}) string {
if vf, isFloat := v.(float64); isFloat {
return fmt.Sprintf("%.4f", vf)
}
return ""
},
}
canvas.PlotRaw("1", types.NumberSeries(1), length)
return canvas
}
func (s *Strategy) DrawCumPNL(cumProfit types.Series) *types.Canvas {
return nil
canvas := types.NewCanvas(s.InstanceID())
canvas.PlotRaw("cummulative pnl", cumProfit, cumProfit.Length())
canvas.YAxis = chart.YAxis{
ValueFormatter: func(v interface{}) string {
if vf, isFloat := v.(float64); isFloat {
return fmt.Sprintf("%.4f", vf)
}
return ""
},
}
return canvas
}
func (s *Strategy) Draw() {
func (s *Strategy) Draw(store *bbgo.SerialMarketDataStore, profit, cumProfit types.Series) {
canvas := s.DrawIndicators(store)
f, err := os.Create(s.GraphIndicatorPath)
if err != nil {
log.WithError(err).Errorf("cannot create on path " + s.GraphIndicatorPath)
return
}
defer f.Close()
if err = canvas.Render(chart.PNG, f); err != nil {
log.WithError(err).Errorf("cannot render elliottwave")
}
canvas = s.DrawPNL(profit)
f, err = os.Create(s.GraphPNLPath)
if err != nil {
log.WithError(err).Errorf("cannot create on path " + s.GraphPNLPath)
return
}
defer f.Close()
if err = canvas.Render(chart.PNG, f); err != nil {
log.WithError(err).Errorf("cannot render pnl")
return
}
canvas = s.DrawCumPNL(cumProfit)
f, err = os.Create(s.GraphCumPNLPath)
if err != nil {
log.WithError(err).Errorf("cannot create on path " + s.GraphCumPNLPath)
return
}
defer f.Close()
if err = canvas.Render(chart.PNG, f); err != nil {
log.WithError(err).Errorf("cannot render cumpnl")
}
}

View File

@ -14,12 +14,10 @@ import (
"github.com/c9s/bbgo/pkg/datatype/floats"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/interact"
"github.com/c9s/bbgo/pkg/strategy"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
"github.com/sirupsen/logrus"
"github.com/wcharczuk/go-chart/v2"
)
const ID = "elliottwave"
@ -51,6 +49,12 @@ type Strategy struct {
WindowSlow int `json:"windowSlow"`
PendingMinutes int `json:"pendingMinutes"`
// whether to draw graph or not by the end of backtest
DrawGraph bool `json:"drawGraph"`
GraphIndicatorPath string `json:"graphIndicatorPath"`
GraphPNLPath string `json:"graphPNLPath"`
GraphCumPNLPath string `json:"graphCumPNLPath"`
*bbgo.Environment
*bbgo.GeneralOrderExecutor
*types.Position `persistence:"position"`
@ -347,24 +351,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if !ok {
panic("cannot get 1m history")
}
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
canvas := s.DrawIndicators(store)
if canvas == nil {
reply.Message("cannot render indicators")
return
}
var buffer bytes.Buffer
if err := canvas.Render(chart.PNG, &buffer); err != nil {
log.WithError(err).Errorf("cannot render indicators in ewo")
reply.Message(fmt.Sprintf("[error] cannot render indicators in ewo: %v", err))
return
}
bbgo.SendPhoto(&buffer)
})
if err := s.initIndicators(store); err != nil {
log.WithError(err).Errorf("initIndicator failed")
return nil
}
s.InitDrawCommands(store, &profit, &cumProfit)
store.OnKLineClosed(func(kline types.KLine) {
s.minutesCounter = int(kline.StartTime.Time().Sub(s.startTime).Minutes())
if kline.Interval == types.Interval1m {
@ -381,6 +372,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
fmt.Fprintln(&buffer, s.TradeStats.BriefString())
os.Stdout.Write(buffer.Bytes())
if s.DrawGraph {
s.Draw(store, &profit, &cumProfit)
}
wg.Done()
})
return nil
@ -442,12 +436,13 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.smartCancel(ctx, pricef)
atr := s.atr.Last()
ewo := types.Array(s.ewo, 3)
shortCondition := ewo[0] < ewo[1] && ewo[1] > ewo[2] || s.sellPrice == 0 && ewo[0] < ewo[1] && ewo[1] < ewo[2]
longCondition := ewo[0] > ewo[1] && ewo[1] < ewo[2] || s.buyPrice == 0 && ewo[0] > ewo[1] && ewo[1] > ewo[2]
exitShortCondition := s.sellPrice > 0 && !shortCondition && s.sellPrice*(1.+stoploss) <= highf || s.trailingCheck(highf, "short")
exitLongCondition := s.buyPrice > 0 && !longCondition && s.buyPrice*(1.-stoploss) >= lowf || s.trailingCheck(lowf, "long")
exitShortCondition := s.sellPrice > 0 && !shortCondition && s.sellPrice*(1.+stoploss) <= highf || s.sellPrice+atr <= sourcef || s.trailingCheck(highf, "short")
exitLongCondition := s.buyPrice > 0 && !longCondition && s.buyPrice*(1.-stoploss) >= lowf || s.buyPrice-atr >= sourcef || s.trailingCheck(lowf, "long")
if exitShortCondition || exitLongCondition {
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {