mirror of
https://github.com/c9s/bbgo.git
synced 2024-09-20 08:11:08 +00:00
fix: unlimited length of indicators, add draw elapsed to drift
This commit is contained in:
parent
493b81f16c
commit
b2e867e51c
|
@ -68,6 +68,7 @@ exchangeStrategies:
|
||||||
graphPNLDeductFee: false
|
graphPNLDeductFee: false
|
||||||
graphPNLPath: "./pnl.png"
|
graphPNLPath: "./pnl.png"
|
||||||
graphCumPNLPath: "./cumpnl.png"
|
graphCumPNLPath: "./cumpnl.png"
|
||||||
|
graphElapsedPath: "./elapsed.png"
|
||||||
#exits:
|
#exits:
|
||||||
# - roiStopLoss:
|
# - roiStopLoss:
|
||||||
# percentage: 0.35%
|
# percentage: 0.35%
|
||||||
|
|
|
@ -8,6 +8,9 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MaxNumOfATR = 1000
|
||||||
|
const MaxNumOfATRTruncateSize = 500
|
||||||
|
|
||||||
//go:generate callbackgen -type ATR
|
//go:generate callbackgen -type ATR
|
||||||
type ATR struct {
|
type ATR struct {
|
||||||
types.SeriesBase
|
types.SeriesBase
|
||||||
|
@ -73,6 +76,9 @@ func (inc *ATR) Update(high, low, cloze float64) {
|
||||||
inc.RMA.Update(trueRange)
|
inc.RMA.Update(trueRange)
|
||||||
atr := inc.RMA.Last()
|
atr := inc.RMA.Last()
|
||||||
inc.PercentageVolatility.Push(atr / cloze)
|
inc.PercentageVolatility.Push(atr / cloze)
|
||||||
|
if len(inc.PercentageVolatility) > MaxNumOfATR {
|
||||||
|
inc.PercentageVolatility = inc.PercentageVolatility[MaxNumOfATRTruncateSize-1:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inc *ATR) Last() float64 {
|
func (inc *ATR) Last() float64 {
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// These numbers should be aligned with bbgo MaxNumOfKLines and MaxNumOfKLinesTruncate
|
// These numbers should be aligned with bbgo MaxNumOfKLines and MaxNumOfKLinesTruncate
|
||||||
const MaxNumOfEWMA = 5_000
|
const MaxNumOfEWMA = 1_000
|
||||||
const MaxNumOfEWMATruncateSize = 100
|
const MaxNumOfEWMATruncateSize = 500
|
||||||
|
|
||||||
//go:generate callbackgen -type EWMA
|
//go:generate callbackgen -type EWMA
|
||||||
type EWMA struct {
|
type EWMA struct {
|
||||||
|
|
|
@ -7,6 +7,9 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MaxNumOfRMA = 1000
|
||||||
|
const MaxNumOfRMATruncateSize = 500
|
||||||
|
|
||||||
// Running Moving Average
|
// Running Moving Average
|
||||||
// Refer: https://github.com/twopirllc/pandas-ta/blob/main/pandas_ta/overlap/rma.py#L5
|
// Refer: https://github.com/twopirllc/pandas-ta/blob/main/pandas_ta/overlap/rma.py#L5
|
||||||
// Refer: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html#pandas-dataframe-ewm
|
// Refer: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html#pandas-dataframe-ewm
|
||||||
|
@ -62,6 +65,9 @@ func (inc *RMA) Update(x float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
inc.Values.Push(inc.tmp)
|
inc.Values.Push(inc.tmp)
|
||||||
|
if len(inc.Values) > MaxNumOfRMA {
|
||||||
|
inc.Values = inc.Values[MaxNumOfRMATruncateSize-1:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inc *RMA) Last() float64 {
|
func (inc *RMA) Last() float64 {
|
||||||
|
|
|
@ -65,6 +65,9 @@ func (inc *SMA) Update(value float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
inc.Values.Push(types.Mean(inc.rawValues))
|
inc.Values.Push(types.Mean(inc.rawValues))
|
||||||
|
if len(inc.Values) > MaxNumOfSMA {
|
||||||
|
inc.Values = inc.Values[MaxNumOfSMATruncateSize-1:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inc *SMA) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) {
|
func (inc *SMA) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) {
|
||||||
|
|
|
@ -7,6 +7,9 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MaxNumOfStdev = 600
|
||||||
|
const MaxNumOfStdevTruncateSize = 300
|
||||||
|
|
||||||
//go:generate callbackgen -type StdDev
|
//go:generate callbackgen -type StdDev
|
||||||
type StdDev struct {
|
type StdDev struct {
|
||||||
types.SeriesBase
|
types.SeriesBase
|
||||||
|
@ -49,6 +52,9 @@ func (inc *StdDev) Update(value float64) {
|
||||||
|
|
||||||
var std = inc.rawValues.Stdev()
|
var std = inc.rawValues.Stdev()
|
||||||
inc.Values.Push(std)
|
inc.Values.Push(std)
|
||||||
|
if len(inc.Values) > MaxNumOfStdev {
|
||||||
|
inc.Values = inc.Values[MaxNumOfStdevTruncateSize-1:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inc *StdDev) PushK(k types.KLine) {
|
func (inc *StdDev) PushK(k types.KLine) {
|
||||||
|
|
175
pkg/strategy/drift/draw.go
Normal file
175
pkg/strategy/drift/draw.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package drift
|
||||||
|
|
||||||
|
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(profit, cumProfit types.Series) {
|
||||||
|
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
|
||||||
|
go func() {
|
||||||
|
canvas := s.DrawIndicators(s.frameKLine.StartTime)
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||||
|
log.WithError(err).Errorf("cannot render indicators in drift")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bbgo.SendPhoto(&buffer)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) {
|
||||||
|
go func() {
|
||||||
|
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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bbgo.SendPhoto(&buffer)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) {
|
||||||
|
go func() {
|
||||||
|
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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bbgo.SendPhoto(&buffer)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
bbgo.RegisterCommand("/elapsed", "Draw Elapsed time for handlers for each kline close event", func(reply interact.Reply) {
|
||||||
|
go func() {
|
||||||
|
canvas := s.DrawElapsed()
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||||
|
log.WithError(err).Errorf("cannot render elapsed in drift")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bbgo.SendPhoto(&buffer)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) DrawIndicators(time types.Time) *types.Canvas {
|
||||||
|
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
|
||||||
|
Length := s.priceLines.Length()
|
||||||
|
if Length > 300 {
|
||||||
|
Length = 300
|
||||||
|
}
|
||||||
|
log.Infof("draw indicators with %d data", Length)
|
||||||
|
mean := s.priceLines.Mean(Length)
|
||||||
|
highestPrice := s.priceLines.Minus(mean).Abs().Highest(Length)
|
||||||
|
highestDrift := s.drift.Abs().Highest(Length)
|
||||||
|
hi := s.drift.drift.Abs().Highest(Length)
|
||||||
|
ratio := highestPrice / highestDrift
|
||||||
|
|
||||||
|
// canvas.Plot("upband", s.ma.Add(s.stdevHigh), time, Length)
|
||||||
|
canvas.Plot("ma", s.ma, time, Length)
|
||||||
|
// canvas.Plot("downband", s.ma.Minus(s.stdevLow), time, Length)
|
||||||
|
fmt.Printf("%f %f\n", highestPrice, hi)
|
||||||
|
|
||||||
|
canvas.Plot("trend", s.trendLine, time, Length)
|
||||||
|
canvas.Plot("drift", s.drift.Mul(ratio).Add(mean), time, Length)
|
||||||
|
canvas.Plot("driftOrig", s.drift.drift.Mul(highestPrice/hi).Add(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 {
|
||||||
|
canvas := types.NewCanvas(s.InstanceID())
|
||||||
|
log.Errorf("pnl Highest: %f, Lowest: %f", types.Highest(profit, profit.Length()), types.Lowest(profit, profit.Length()))
|
||||||
|
length := profit.Length()
|
||||||
|
if s.GraphPNLDeductFee {
|
||||||
|
canvas.PlotRaw("pnl % (with Fee Deducted)", profit, length)
|
||||||
|
} else {
|
||||||
|
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 {
|
||||||
|
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) DrawElapsed() *types.Canvas {
|
||||||
|
canvas := types.NewCanvas(s.InstanceID())
|
||||||
|
canvas.PlotRaw("elapsed time(ms)", s.elapsed, s.elapsed.Length())
|
||||||
|
return canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) Draw(time types.Time, profit types.Series, cumProfit types.Series) {
|
||||||
|
canvas := s.DrawIndicators(time)
|
||||||
|
f, err := os.Create(s.CanvasPath)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("cannot create on %s", s.CanvasPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||||
|
log.WithError(err).Errorf("cannot render in drift")
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
canvas = s.DrawPNL(profit)
|
||||||
|
f, err = os.Create(s.GraphPNLPath)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("open pnl")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||||
|
log.WithError(err).Errorf("render pnl")
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
canvas = s.DrawCumPNL(cumProfit)
|
||||||
|
f, err = os.Create(s.GraphCumPNLPath)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("open cumpnl")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||||
|
log.WithError(err).Errorf("render cumpnl")
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
canvas = s.DrawElapsed()
|
||||||
|
f, err = os.Create(s.GraphElapsedPath)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("open elapsed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||||
|
log.WithError(err).Errorf("render elapsed")
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/wcharczuk/go-chart/v2"
|
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
|
@ -68,6 +67,7 @@ type Strategy struct {
|
||||||
p *types.Position
|
p *types.Position
|
||||||
MinInterval types.Interval `json:"MinInterval"` // minimum interval referred for doing stoploss/trailing exists and updating highest/lowest
|
MinInterval types.Interval `json:"MinInterval"` // minimum interval referred for doing stoploss/trailing exists and updating highest/lowest
|
||||||
|
|
||||||
|
elapsed *types.Queue
|
||||||
priceLines *types.Queue
|
priceLines *types.Queue
|
||||||
trendLine types.UpdatableSeriesExtend
|
trendLine types.UpdatableSeriesExtend
|
||||||
ma types.UpdatableSeriesExtend
|
ma types.UpdatableSeriesExtend
|
||||||
|
@ -78,8 +78,8 @@ type Strategy struct {
|
||||||
midPrice fixedpoint.Value // the midPrice is the average of bestBid and bestAsk in public orderbook
|
midPrice fixedpoint.Value // the midPrice is the average of bestBid and bestAsk in public orderbook
|
||||||
lock sync.RWMutex `ignore:"true"` // lock for midPrice
|
lock sync.RWMutex `ignore:"true"` // lock for midPrice
|
||||||
positionLock sync.RWMutex `ignore:"true"` // lock for highest/lowest and p
|
positionLock sync.RWMutex `ignore:"true"` // lock for highest/lowest and p
|
||||||
|
pendingLock sync.Mutex `ignore:"true"`
|
||||||
startTime time.Time // trading start time
|
startTime time.Time // trading start time
|
||||||
counter int // number of MinInterval since startTime
|
|
||||||
maxCounterBuyCanceled int // the largest counter of the order on the buy side been cancelled. meaning the latest cancelled buy order.
|
maxCounterBuyCanceled int // the largest counter of the order on the buy side been cancelled. meaning the latest cancelled buy order.
|
||||||
maxCounterSellCanceled int // the largest counter of the order on the sell side been cancelled. meaning the latest cancelled sell order.
|
maxCounterSellCanceled int // the largest counter of the order on the sell side been cancelled. meaning the latest cancelled sell order.
|
||||||
orderPendingCounter map[uint64]int // records the timepoint when the orders are created, using the counter at the time.
|
orderPendingCounter map[uint64]int // records the timepoint when the orders are created, using the counter at the time.
|
||||||
|
@ -114,10 +114,11 @@ type Strategy struct {
|
||||||
// This is not related to trade but for statistics graph generation
|
// This is not related to trade but for statistics graph generation
|
||||||
// Will deduct fee in percentage from every trade
|
// Will deduct fee in percentage from every trade
|
||||||
GraphPNLDeductFee bool `json:"graphPNLDeductFee"`
|
GraphPNLDeductFee bool `json:"graphPNLDeductFee"`
|
||||||
CanvasPath string `json:"canvasPath"` // backtest related. the path to store the indicator graph
|
CanvasPath string `json:"canvasPath"` // backtest related. the path to store the indicator graph
|
||||||
GraphPNLPath string `json:"graphPNLPath"` // backtest related. the path to store the pnl % graph per trade graph.
|
GraphPNLPath string `json:"graphPNLPath"` // backtest related. the path to store the pnl % graph per trade graph.
|
||||||
GraphCumPNLPath string `json:"graphCumPNLPath"` // backtest related. the path to store the asset changes in graph
|
GraphCumPNLPath string `json:"graphCumPNLPath"` // backtest related. the path to store the asset changes in graph
|
||||||
GenerateGraph bool `json:"generateGraph"` // whether to generate graph when shutdown
|
GraphElapsedPath string `json:"graphElapsedPath"` // the path to store the elapsed time in ms
|
||||||
|
GenerateGraph bool `json:"generateGraph"` // whether to generate graph when shutdown
|
||||||
|
|
||||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||||
Session *bbgo.ExchangeSession
|
Session *bbgo.ExchangeSession
|
||||||
|
@ -264,7 +265,7 @@ func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, error) {
|
func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64, syscounter int) (int, error) {
|
||||||
nonTraded := s.GeneralOrderExecutor.ActiveMakerOrders().Orders()
|
nonTraded := s.GeneralOrderExecutor.ActiveMakerOrders().Orders()
|
||||||
if len(nonTraded) > 0 {
|
if len(nonTraded) > 0 {
|
||||||
if len(nonTraded) > 1 {
|
if len(nonTraded) > 1 {
|
||||||
|
@ -276,17 +277,21 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, e
|
||||||
if order.Status != types.OrderStatusNew && order.Status != types.OrderStatusPartiallyFilled {
|
if order.Status != types.OrderStatusNew && order.Status != types.OrderStatusPartiallyFilled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.counter)
|
s.pendingLock.Lock()
|
||||||
if s.counter-s.orderPendingCounter[order.OrderID] > s.PendingMinInterval {
|
counter := s.orderPendingCounter[order.OrderID]
|
||||||
|
s.pendingLock.Unlock()
|
||||||
|
|
||||||
|
log.Warnf("%v | counter: %d, system: %d", order, counter, syscounter)
|
||||||
|
if syscounter-counter > s.PendingMinInterval {
|
||||||
toCancel = true
|
toCancel = true
|
||||||
} else if order.Side == types.SideTypeBuy {
|
} else if order.Side == types.SideTypeBuy {
|
||||||
// 75% of the probability
|
// 75% of the probability
|
||||||
if order.Price.Float64()+s.stdevHigh.Last()*2 <= pricef {
|
if order.Price.Float64()+atr*2 <= pricef {
|
||||||
toCancel = true
|
toCancel = true
|
||||||
}
|
}
|
||||||
} else if order.Side == types.SideTypeSell {
|
} else if order.Side == types.SideTypeSell {
|
||||||
// 75% of the probability
|
// 75% of the probability
|
||||||
if order.Price.Float64()-s.stdevLow.Last()*2 >= pricef {
|
if order.Price.Float64()-atr*2 >= pricef {
|
||||||
toCancel = true
|
toCancel = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -297,16 +302,19 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, e
|
||||||
err := s.GeneralOrderExecutor.CancelNoWait(ctx)
|
err := s.GeneralOrderExecutor.CancelNoWait(ctx)
|
||||||
// TODO: clean orderPendingCounter on cancel/trade
|
// TODO: clean orderPendingCounter on cancel/trade
|
||||||
for _, order := range nonTraded {
|
for _, order := range nonTraded {
|
||||||
|
s.pendingLock.Lock()
|
||||||
|
counter := s.orderPendingCounter[order.OrderID]
|
||||||
|
delete(s.orderPendingCounter, order.OrderID)
|
||||||
|
s.pendingLock.Unlock()
|
||||||
if order.Side == types.SideTypeSell {
|
if order.Side == types.SideTypeSell {
|
||||||
if s.maxCounterSellCanceled < s.orderPendingCounter[order.OrderID] {
|
if s.maxCounterSellCanceled < counter {
|
||||||
s.maxCounterSellCanceled = s.orderPendingCounter[order.OrderID]
|
s.maxCounterSellCanceled = counter
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if s.maxCounterBuyCanceled < s.orderPendingCounter[order.OrderID] {
|
if s.maxCounterBuyCanceled < counter {
|
||||||
s.maxCounterBuyCanceled = s.orderPendingCounter[order.OrderID]
|
s.maxCounterBuyCanceled = counter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(s.orderPendingCounter, order.OrderID)
|
|
||||||
}
|
}
|
||||||
log.Warnf("cancel all %v", err)
|
log.Warnf("cancel all %v", err)
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -390,102 +398,6 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) DrawIndicators(time types.Time) *types.Canvas {
|
|
||||||
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
|
|
||||||
Length := s.priceLines.Length()
|
|
||||||
if Length > 300 {
|
|
||||||
Length = 300
|
|
||||||
}
|
|
||||||
log.Infof("draw indicators with %d data", Length)
|
|
||||||
mean := s.priceLines.Mean(Length)
|
|
||||||
highestPrice := s.priceLines.Minus(mean).Abs().Highest(Length)
|
|
||||||
highestDrift := s.drift.Abs().Highest(Length)
|
|
||||||
hi := s.drift.drift.Abs().Highest(Length)
|
|
||||||
ratio := highestPrice / highestDrift
|
|
||||||
|
|
||||||
// canvas.Plot("upband", s.ma.Add(s.stdevHigh), time, Length)
|
|
||||||
canvas.Plot("ma", s.ma, time, Length)
|
|
||||||
// canvas.Plot("downband", s.ma.Minus(s.stdevLow), time, Length)
|
|
||||||
fmt.Printf("%f %f\n", highestPrice, hi)
|
|
||||||
|
|
||||||
canvas.Plot("trend", s.trendLine, time, Length)
|
|
||||||
canvas.Plot("drift", s.drift.Mul(ratio).Add(mean), time, Length)
|
|
||||||
canvas.Plot("driftOrig", s.drift.drift.Mul(highestPrice/hi).Add(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 {
|
|
||||||
canvas := types.NewCanvas(s.InstanceID())
|
|
||||||
log.Errorf("pnl Highest: %f, Lowest: %f", types.Highest(profit, profit.Length()), types.Lowest(profit, profit.Length()))
|
|
||||||
length := profit.Length()
|
|
||||||
if s.GraphPNLDeductFee {
|
|
||||||
canvas.PlotRaw("pnl % (with Fee Deducted)", profit, length)
|
|
||||||
} else {
|
|
||||||
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 {
|
|
||||||
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(time types.Time, profit types.Series, cumProfit types.Series) {
|
|
||||||
canvas := s.DrawIndicators(time)
|
|
||||||
f, err := os.Create(s.CanvasPath)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Errorf("cannot create on %s", s.CanvasPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
|
||||||
log.WithError(err).Errorf("cannot render in drift")
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas = s.DrawPNL(profit)
|
|
||||||
f, err = os.Create(s.GraphPNLPath)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Errorf("open pnl")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
|
||||||
log.WithError(err).Errorf("render pnl")
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas = s.DrawCumPNL(cumProfit)
|
|
||||||
f, err = os.Create(s.GraphCumPNLPath)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Errorf("open cumpnl")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
|
||||||
log.WithError(err).Errorf("render cumpnl")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sending new rebalance orders cost too much.
|
// Sending new rebalance orders cost too much.
|
||||||
// Modify the position instead to expect the strategy itself rebalance on Close
|
// Modify the position instead to expect the strategy itself rebalance on Close
|
||||||
func (s *Strategy) Rebalance(ctx context.Context) {
|
func (s *Strategy) Rebalance(ctx context.Context) {
|
||||||
|
@ -548,7 +460,7 @@ func (s *Strategy) CalcAssetValue(price fixedpoint.Value) fixedpoint.Value {
|
||||||
return balances[s.Market.BaseCurrency].Total().Mul(price).Add(balances[s.Market.QuoteCurrency].Total())
|
return balances[s.Market.BaseCurrency].Total().Mul(price).Add(balances[s.Market.QuoteCurrency].Total())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine) {
|
func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine, counter int) {
|
||||||
s.klineMin.Set(&kline)
|
s.klineMin.Set(&kline)
|
||||||
if s.Status != types.StrategyStatusRunning {
|
if s.Status != types.StrategyStatusRunning {
|
||||||
return
|
return
|
||||||
|
@ -571,7 +483,7 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine) {
|
||||||
|
|
||||||
numPending := 0
|
numPending := 0
|
||||||
var err error
|
var err error
|
||||||
if numPending, err = s.smartCancel(ctx, pricef, atr); err != nil {
|
if numPending, err = s.smartCancel(ctx, pricef, atr, counter); err != nil {
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -589,28 +501,37 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine, counter int) {
|
||||||
|
start := time.Now()
|
||||||
|
defer func() {
|
||||||
|
end := time.Now()
|
||||||
|
elapsed := end.Sub(start)
|
||||||
|
s.elapsed.Update(float64(elapsed) / 1000000)
|
||||||
|
}()
|
||||||
s.frameKLine.Set(&kline)
|
s.frameKLine.Set(&kline)
|
||||||
|
|
||||||
source := s.GetSource(&kline)
|
source := s.GetSource(&kline)
|
||||||
sourcef := source.Float64()
|
sourcef := source.Float64()
|
||||||
|
|
||||||
s.priceLines.Update(sourcef)
|
s.priceLines.Update(sourcef)
|
||||||
s.ma.Update(sourcef)
|
//s.ma.Update(sourcef)
|
||||||
s.trendLine.Update(sourcef)
|
s.trendLine.Update(sourcef)
|
||||||
|
|
||||||
s.drift.Update(sourcef, kline.Volume.Abs().Float64())
|
s.drift.Update(sourcef, kline.Volume.Abs().Float64())
|
||||||
|
|
||||||
s.atr.PushK(kline)
|
s.atr.PushK(kline)
|
||||||
|
|
||||||
atr := s.atr.Last()
|
atr := s.atr.Last()
|
||||||
price := s.getLastPrice()
|
|
||||||
|
price := kline.Close //s.getLastPrice()
|
||||||
pricef := price.Float64()
|
pricef := price.Float64()
|
||||||
lowf := math.Min(kline.Low.Float64(), pricef)
|
lowf := math.Min(kline.Low.Float64(), pricef)
|
||||||
highf := math.Max(kline.High.Float64(), pricef)
|
highf := math.Max(kline.High.Float64(), pricef)
|
||||||
lowdiff := s.ma.Last() - lowf
|
lowdiff := pricef - lowf
|
||||||
s.stdevLow.Update(lowdiff)
|
s.stdevLow.Update(lowdiff)
|
||||||
highdiff := highf - s.ma.Last()
|
highdiff := highf - pricef
|
||||||
s.stdevHigh.Update(highdiff)
|
s.stdevHigh.Update(highdiff)
|
||||||
|
|
||||||
drift := s.drift.Array(2)
|
drift := s.drift.Array(2)
|
||||||
|
|
||||||
if len(drift) < 2 || len(drift) < s.PredictOffset {
|
if len(drift) < 2 || len(drift) < s.PredictOffset {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -623,7 +544,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("highdiff: %3.2f ma: %.2f, open: %8v, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(), s.ma.Last(), kline.Open, kline.Close, kline.High, kline.Low, kline.StartTime, kline.EndTime)
|
log.Infof("highdiff: %3.2f open: %8v, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(), kline.Open, kline.Close, kline.High, kline.Low, kline.StartTime, kline.EndTime)
|
||||||
|
|
||||||
s.positionLock.Lock()
|
s.positionLock.Lock()
|
||||||
if s.lowestPrice > 0 && lowf < s.lowestPrice {
|
if s.lowestPrice > 0 && lowf < s.lowestPrice {
|
||||||
|
@ -667,14 +588,14 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
if exitCondition || longCondition || shortCondition {
|
if exitCondition || longCondition || shortCondition {
|
||||||
var err error
|
var err error
|
||||||
var hold int
|
var hold int
|
||||||
if hold, err = s.smartCancel(ctx, sourcef, atr); err != nil {
|
if hold, err = s.smartCancel(ctx, pricef, atr, counter); err != nil {
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
}
|
}
|
||||||
if hold > 0 {
|
if hold > 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := s.smartCancel(ctx, sourcef, atr); err != nil {
|
if _, err := s.smartCancel(ctx, pricef, atr, counter); err != nil {
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -692,11 +613,12 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
opt.Long = true
|
opt.Long = true
|
||||||
opt.LimitOrder = true
|
opt.LimitOrder = true
|
||||||
// force to use market taker
|
// force to use market taker
|
||||||
if s.counter-s.maxCounterBuyCanceled <= 1 {
|
if counter-s.maxCounterBuyCanceled <= s.PendingMinInterval {
|
||||||
opt.LimitOrder = false
|
opt.LimitOrder = false
|
||||||
}
|
}
|
||||||
opt.Price = source
|
opt.Price = source
|
||||||
opt.Tags = []string{"long"}
|
opt.Tags = []string{"long"}
|
||||||
|
|
||||||
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs := filterErrors(multierr.Errors(err))
|
errs := filterErrors(multierr.Errors(err))
|
||||||
|
@ -706,9 +628,16 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("orders %v", createdOrders)
|
log.Infof("orders %v", createdOrders)
|
||||||
if createdOrders != nil {
|
if createdOrders != nil {
|
||||||
s.orderPendingCounter[createdOrders[0].OrderID] = s.counter
|
for _, o := range createdOrders {
|
||||||
|
if o.Status == types.OrderStatusNew || o.Status == types.OrderStatusPartiallyFilled {
|
||||||
|
s.pendingLock.Lock()
|
||||||
|
s.orderPendingCounter[o.OrderID] = counter
|
||||||
|
s.pendingLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -724,7 +653,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
opt.Short = true
|
opt.Short = true
|
||||||
opt.Price = source
|
opt.Price = source
|
||||||
opt.LimitOrder = true
|
opt.LimitOrder = true
|
||||||
if s.counter-s.maxCounterSellCanceled <= 1 {
|
if counter-s.maxCounterSellCanceled <= s.PendingMinInterval {
|
||||||
opt.LimitOrder = false
|
opt.LimitOrder = false
|
||||||
}
|
}
|
||||||
opt.Tags = []string{"short"}
|
opt.Tags = []string{"short"}
|
||||||
|
@ -738,7 +667,13 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
|
||||||
}
|
}
|
||||||
log.Infof("orders %v", createdOrders)
|
log.Infof("orders %v", createdOrders)
|
||||||
if createdOrders != nil {
|
if createdOrders != nil {
|
||||||
s.orderPendingCounter[createdOrders[0].OrderID] = s.counter
|
for _, o := range createdOrders {
|
||||||
|
if o.Status == types.OrderStatusNew || o.Status == types.OrderStatusPartiallyFilled {
|
||||||
|
s.pendingLock.Lock()
|
||||||
|
s.orderPendingCounter[o.OrderID] = counter
|
||||||
|
s.pendingLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -787,7 +722,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.GeneralOrderExecutor.Bind()
|
s.GeneralOrderExecutor.Bind()
|
||||||
|
|
||||||
s.orderPendingCounter = make(map[uint64]int)
|
s.orderPendingCounter = make(map[uint64]int)
|
||||||
s.counter = 0
|
|
||||||
|
|
||||||
// Exit methods from config
|
// Exit methods from config
|
||||||
for _, method := range s.ExitMethods {
|
for _, method := range s.ExitMethods {
|
||||||
|
@ -845,47 +779,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.frameKLine = &types.KLine{}
|
s.frameKLine = &types.KLine{}
|
||||||
s.klineMin = &types.KLine{}
|
s.klineMin = &types.KLine{}
|
||||||
s.priceLines = types.NewQueue(300)
|
s.priceLines = types.NewQueue(300)
|
||||||
|
s.elapsed = types.NewQueue(60000)
|
||||||
|
|
||||||
s.initTickerFunctions(ctx)
|
s.initTickerFunctions(ctx)
|
||||||
s.startTime = s.Environment.StartTime()
|
s.startTime = s.Environment.StartTime()
|
||||||
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1d, s.startTime))
|
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1d, s.startTime))
|
||||||
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1w, s.startTime))
|
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1w, s.startTime))
|
||||||
|
|
||||||
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
|
s.InitDrawCommands(&profit, &cumProfit)
|
||||||
go func() {
|
|
||||||
canvas := s.DrawIndicators(s.frameKLine.StartTime)
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
|
||||||
log.WithError(err).Errorf("cannot render indicators in drift")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bbgo.SendPhoto(&buffer)
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
|
|
||||||
bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) {
|
|
||||||
go func() {
|
|
||||||
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")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bbgo.SendPhoto(&buffer)
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
|
|
||||||
bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) {
|
|
||||||
go func() {
|
|
||||||
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")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bbgo.SendPhoto(&buffer)
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
|
|
||||||
bbgo.RegisterCommand("/config", "Show latest config", func(reply interact.Reply) {
|
bbgo.RegisterCommand("/config", "Show latest config", func(reply interact.Reply) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
@ -922,12 +823,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//var lastK types.KLine
|
||||||
store.OnKLineClosed(func(kline types.KLine) {
|
store.OnKLineClosed(func(kline types.KLine) {
|
||||||
s.counter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Milliseconds()) / s.MinInterval.Milliseconds()
|
counter := int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Milliseconds()) / s.MinInterval.Milliseconds()
|
||||||
if kline.Interval == s.Interval {
|
if kline.Interval == s.Interval {
|
||||||
s.klineHandler(ctx, kline)
|
s.klineHandler(ctx, kline, counter)
|
||||||
} else if kline.Interval == s.MinInterval {
|
} else if kline.Interval == s.MinInterval {
|
||||||
s.klineHandlerMin(ctx, kline)
|
s.klineHandlerMin(ctx, kline, counter)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ func (s *Strategy) InitDrawCommands(store *bbgo.SerialMarketDataStore, profit, c
|
||||||
go func() {
|
go func() {
|
||||||
canvas := s.DrawIndicators(store)
|
canvas := s.DrawIndicators(store)
|
||||||
if canvas == nil {
|
if canvas == nil {
|
||||||
reply.Message("cannot render indicators")
|
reply.Send("cannot render indicators")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
|
@ -145,7 +145,7 @@ func (inc *CCISTOCH) Update(cloze float64) {
|
||||||
|
|
||||||
func (inc *CCISTOCH) BuySignal() bool {
|
func (inc *CCISTOCH) BuySignal() bool {
|
||||||
hasGrey := false
|
hasGrey := false
|
||||||
for i := 0; i < len(inc.ma.Values); i++ {
|
for i := 0; i < inc.ma.Values.Length(); i++ {
|
||||||
v := inc.ma.Index(i)
|
v := inc.ma.Index(i)
|
||||||
if v > inc.filterHigh {
|
if v > inc.filterHigh {
|
||||||
return false
|
return false
|
||||||
|
@ -161,7 +161,7 @@ func (inc *CCISTOCH) BuySignal() bool {
|
||||||
|
|
||||||
func (inc *CCISTOCH) SellSignal() bool {
|
func (inc *CCISTOCH) SellSignal() bool {
|
||||||
hasGrey := false
|
hasGrey := false
|
||||||
for i := 0; i < len(inc.ma.Values); i++ {
|
for i := 0; i < inc.ma.Values.Length(); i++ {
|
||||||
v := inc.ma.Index(i)
|
v := inc.ma.Index(i)
|
||||||
if v < inc.filterLow {
|
if v < inc.filterLow {
|
||||||
return false
|
return false
|
||||||
|
|
Loading…
Reference in New Issue
Block a user