pivotshort: refactor exit methods

Signed-off-by: c9s <yoanlin93@gmail.com>
This commit is contained in:
c9s 2022-06-26 16:31:48 +08:00
parent 47677e303f
commit e9b87f6f1e
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
4 changed files with 150 additions and 61 deletions

View File

@ -0,0 +1,64 @@
package pivotshort
import (
"context"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
type CumulatedVolumeTakeProfit struct {
types.IntervalWindow
Ratio fixedpoint.Value `json:"ratio"`
MinQuoteVolume fixedpoint.Value `json:"minQuoteVolume"`
session *bbgo.ExchangeSession
orderExecutor *bbgo.GeneralOrderExecutor
}
func (s *CumulatedVolumeTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
s.session = session
s.orderExecutor = orderExecutor
position := orderExecutor.Position()
store, _ := session.MarketDataStore(position.Symbol)
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
if kline.Symbol != position.Symbol || kline.Interval != types.Interval1m {
return
}
closePrice := kline.Close
if position.IsClosed() || position.IsDust(closePrice) {
return
}
roi := position.ROI(closePrice)
if roi.Sign() < 0 {
return
}
if klines, ok := store.KLinesOfInterval(s.Interval); ok {
var cbv = fixedpoint.Zero
var cqv = fixedpoint.Zero
for i := 0; i < s.Window; i++ {
last := (*klines)[len(*klines)-1-i]
cqv = cqv.Add(last.QuoteVolume)
cbv = cbv.Add(last.Volume)
}
if cqv.Compare(s.MinQuoteVolume) > 0 {
bbgo.Notify("%s TakeProfit triggered by cumulated volume (window: %d) %f > %f, price = %f",
position.Symbol,
s.Window,
cqv.Float64(),
s.MinQuoteVolume.Float64(), kline.Close.Float64())
_ = orderExecutor.ClosePosition(context.Background(), fixedpoint.One)
return
}
}
})
}

View File

@ -0,0 +1,28 @@
package pivotshort
import "github.com/c9s/bbgo/pkg/bbgo"
type ExitMethod struct {
RoiStopLoss *RoiStopLoss `json:"roiStopLoss"`
ProtectionStopLoss *ProtectionStopLoss `json:"protectionStopLoss"`
RoiTakeProfit *RoiTakeProfit `json:"roiTakeProfit"`
LowerShadowTakeProfit *LowerShadowTakeProfit `json:"lowerShadowTakeProfit"`
CumulatedVolumeTakeProfit *CumulatedVolumeTakeProfit `json:"cumulatedVolumeTakeProfit"`
// MarginSideEffect types.MarginOrderSideEffectType `json:"marginOrderSideEffect"`
}
func (m *ExitMethod) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
if m.ProtectionStopLoss != nil {
m.ProtectionStopLoss.Bind(session, orderExecutor)
} else if m.RoiStopLoss != nil {
m.RoiStopLoss.Bind(session, orderExecutor)
} else if m.RoiTakeProfit != nil {
m.RoiTakeProfit.Bind(session, orderExecutor)
} else if m.LowerShadowTakeProfit != nil {
m.LowerShadowTakeProfit.Bind(session, orderExecutor)
} else if m.CumulatedVolumeTakeProfit != nil {
m.CumulatedVolumeTakeProfit.Bind(session, orderExecutor)
}
}

View File

@ -0,0 +1,53 @@
package pivotshort
import (
"context"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
type LowerShadowTakeProfit struct {
Ratio fixedpoint.Value `json:"ratio"`
session *bbgo.ExchangeSession
orderExecutor *bbgo.GeneralOrderExecutor
}
func (s *LowerShadowTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
s.session = session
s.orderExecutor = orderExecutor
position := orderExecutor.Position()
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
if kline.Symbol != position.Symbol || kline.Interval != types.Interval1m {
return
}
closePrice := kline.Close
if position.IsClosed() || position.IsDust(closePrice) {
return
}
roi := position.ROI(closePrice)
if roi.Sign() < 0 {
return
}
if s.Ratio.IsZero() {
return
}
if kline.GetLowerShadowHeight().Div(kline.Close).Compare(s.Ratio) > 0 {
bbgo.Notify("%s TakeProfit triggered by shadow ratio %f, price = %f",
position.Symbol,
kline.GetLowerShadowRatio().Float64(),
kline.Close.Float64(),
kline)
_ = orderExecutor.ClosePosition(context.Background(), fixedpoint.One)
return
}
})
}

View File

@ -74,20 +74,6 @@ type CumulatedVolume struct {
Window int `json:"window"`
}
type Exit struct {
RoiMinTakeProfitPercentage fixedpoint.Value `json:"roiMinTakeProfitPercentage"`
RoiTakeProfit *RoiTakeProfit `json:"roiTakeProfit"`
RoiStopLoss *RoiStopLoss `json:"roiStopLoss"`
ProtectionStopLoss *ProtectionStopLoss `json:"protectionStopLoss"`
LowerShadowRatio fixedpoint.Value `json:"lowerShadowRatio"`
CumulatedVolume *CumulatedVolume `json:"cumulatedVolume"`
MarginSideEffect types.MarginOrderSideEffectType `json:"marginOrderSideEffect"`
}
type Strategy struct {
*bbgo.Graceful
@ -108,7 +94,7 @@ type Strategy struct {
BounceShort *BounceShort `json:"bounceShort"`
Entry Entry `json:"entry"`
Exit Exit `json:"exit"`
ExitMethods []ExitMethod `json:"exits"`
session *bbgo.ExchangeSession
orderExecutor *bbgo.GeneralOrderExecutor
@ -278,16 +264,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
})
if s.Exit.ProtectionStopLoss != nil {
s.Exit.ProtectionStopLoss.Bind(session, s.orderExecutor)
}
if s.Exit.RoiStopLoss != nil {
s.Exit.RoiStopLoss.Bind(session, s.orderExecutor)
}
if s.Exit.RoiTakeProfit != nil {
s.Exit.RoiTakeProfit.Bind(session, s.orderExecutor)
for _, method := range s.ExitMethods {
method.Bind(session, s.orderExecutor)
}
// Always check whether you can open a short position or not
@ -301,42 +279,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
isPositionOpened := !s.Position.IsClosed() && !s.Position.IsDust(kline.Close)
if isPositionOpened && s.Position.IsShort() {
roi := s.Position.ROI(kline.Close)
if !s.Exit.RoiMinTakeProfitPercentage.IsZero() {
if roi.Compare(s.Exit.RoiMinTakeProfitPercentage) > 0 {
if !s.Exit.LowerShadowRatio.IsZero() && kline.GetLowerShadowHeight().Div(kline.Close).Compare(s.Exit.LowerShadowRatio) > 0 {
bbgo.Notify("%s TakeProfit triggered at price %f: by shadow ratio %f",
s.Symbol,
kline.Close.Float64(),
kline.GetLowerShadowRatio().Float64(), kline)
_ = s.ClosePosition(ctx, fixedpoint.One)
return
} else if s.Exit.CumulatedVolume != nil && s.Exit.CumulatedVolume.Enabled {
if klines, ok := store.KLinesOfInterval(s.Interval); ok {
var cbv = fixedpoint.Zero
var cqv = fixedpoint.Zero
for i := 0; i < s.Exit.CumulatedVolume.Window; i++ {
last := (*klines)[len(*klines)-1-i]
cqv = cqv.Add(last.QuoteVolume)
cbv = cbv.Add(last.Volume)
}
if cqv.Compare(s.Exit.CumulatedVolume.MinQuoteVolume) > 0 {
bbgo.Notify("%s TakeProfit triggered at price %f: by cumulated volume (window: %d) %f > %f",
s.Symbol,
kline.Close.Float64(),
s.Exit.CumulatedVolume.Window,
cqv.Float64(),
s.Exit.CumulatedVolume.MinQuoteVolume.Float64())
_ = s.ClosePosition(ctx, fixedpoint.One)
return
}
}
}
}
}
}
if len(s.pivotLowPrices) == 0 {