From e17535e6513f0f3ac25c9494c8f9a2e68a269212 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 9 Jun 2022 12:25:36 +0800 Subject: [PATCH] pivotshort: fix position close bugs --- pkg/indicator/pivot.go | 11 ++++++++--- pkg/strategy/pivotshort/strategy.go | 27 ++++++++++++++++++--------- pkg/types/position.go | 7 ++++++- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/pkg/indicator/pivot.go b/pkg/indicator/pivot.go index 7ff508639..71a6f35f6 100644 --- a/pkg/indicator/pivot.go +++ b/pkg/indicator/pivot.go @@ -2,9 +2,10 @@ package indicator import ( "fmt" - log "github.com/sirupsen/logrus" "time" + log "github.com/sirupsen/logrus" + "github.com/c9s/bbgo/pkg/types" ) @@ -45,17 +46,19 @@ func (inc *Pivot) calculateAndUpdate(klines []types.KLine) { var end = len(klines) - 1 var lastKLine = klines[end] + // skip old data if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { return } - var recentT = klines[end-(inc.Window-1) : end+1] + recentT := klines[end-(inc.Window-1) : end+1] l, h, err := calculatePivot(recentT, inc.Window, KLineLowPriceMapper, KLineHighPriceMapper) if err != nil { log.WithError(err).Error("can not calculate pivots") return } + inc.Lows.Push(l) inc.Highs.Push(h) @@ -87,8 +90,9 @@ func (inc *Pivot) Bind(updater KLineWindowUpdater) { func calculatePivot(klines []types.KLine, window int, valLow KLineValueMapper, valHigh KLineValueMapper) (float64, float64, error) { length := len(klines) if length == 0 || length < window { - return 0., 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + return 0., 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) } + var lows types.Float64Slice var highs types.Float64Slice for _, k := range klines { @@ -100,6 +104,7 @@ func calculatePivot(klines []types.KLine, window int, valLow KLineValueMapper, v if lows.Min() == lows.Index(int(window/2.)-1) { pl = lows.Min() } + ph := 0. if highs.Max() == highs.Index(int(window/2.)-1) { ph = highs.Max() diff --git a/pkg/strategy/pivotshort/strategy.go b/pkg/strategy/pivotshort/strategy.go index d85f3ec51..7fe3c11be 100644 --- a/pkg/strategy/pivotshort/strategy.go +++ b/pkg/strategy/pivotshort/strategy.go @@ -132,6 +132,9 @@ func canClosePosition(position *types.Position, price fixedpoint.Value) bool { func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { submitOrder := s.Position.NewClosePositionOrder(percentage) // types.SubmitOrder{ + if submitOrder == nil { + return nil + } if s.session.Margin { submitOrder.MarginSideEffect = s.Exit.MarginSideEffect @@ -238,27 +241,34 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se R := kline.Close.Sub(s.Position.AverageCost).Div(s.Position.AverageCost) if R.Compare(s.Exit.StopLossPercentage) > 0 { // SL - s.Notify("%s SL triggered", s.Symbol) - s.ClosePosition(ctx, fixedpoint.One.Div(fixedpoint.NewFromFloat(0.99))) + s.Notify("%s SL triggered at price %f", s.Symbol, kline.Close.Float64()) + s.ClosePosition(ctx, fixedpoint.One) + return } else if R.Compare(s.Exit.TakeProfitPercentage.Neg()) < 0 && kline.GetLowerShadowRatio().Compare(s.Exit.LowerShadowRatio) > 0 { // TP - s.Notify("%s TP triggered", s.Symbol) - s.ClosePosition(ctx, fixedpoint.One.Div(fixedpoint.NewFromFloat(0.99))) + s.Notify("%s TP triggered at price %f", s.Symbol, kline.Close.Float64()) + s.ClosePosition(ctx, fixedpoint.One) + return } } if len(s.pivotLowPrices) > 0 { - latestPivotLow := s.pivotLowPrices[len(s.pivotLowPrices)-1] + lastLow := s.pivotLowPrices[len(s.pivotLowPrices)-1] + if kline.Close.Compare(lastLow) < 0 { + s.Notify("%s price %f breaks the previous low %f, submitting market sell to open a short position", s.Symbol, kline.Close.Float64(), lastLow.Float64()) + + if !s.Position.IsClosed() && !s.Position.IsDust(kline.Close) { + s.Notify("skip opening %s position, which is not closed", s.Symbol, s.Position) + return + } - if kline.Close.Compare(latestPivotLow) > 0 && (s.Position.IsClosed() || s.Position.IsDust(kline.Close)) { if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil { log.WithError(err).Errorf("graceful cancel order error") } - s.Notify("price breaks the previous low, submitting market sell to open a short position") + s.placeMarketSell(ctx, orderExecutor) } } - }) session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { @@ -271,7 +281,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.LastLow = fixedpoint.NewFromFloat(s.pivot.LastLow()) s.pivotLowPrices = append(s.pivotLowPrices, s.LastLow) } - }) return nil diff --git a/pkg/types/position.go b/pkg/types/position.go index c672e9d81..f3aa5e3f9 100644 --- a/pkg/types/position.go +++ b/pkg/types/position.go @@ -122,7 +122,12 @@ func (p *Position) NewProfit(trade Trade, profit, netProfit fixedpoint.Value) Pr func (p *Position) NewClosePositionOrder(percentage fixedpoint.Value) *SubmitOrder { base := p.GetBase() - quantity := base.Mul(percentage).Abs() + + quantity := base.Abs() + if percentage.Compare(fixedpoint.One) < 0 { + quantity = quantity.Mul(percentage) + } + if quantity.Compare(p.Market.MinQuantity) < 0 { return nil }