mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
pivotshort: add roiMinTakeProfitPercentage option and cumulatedVolume option
This commit is contained in:
parent
53913ede23
commit
35a58268cf
|
@ -34,17 +34,22 @@ exchangeStrategies:
|
|||
# roiStopLossPercentage is the stop loss percentage of the position ROI (currently the price change)
|
||||
roiStopLossPercentage: 1%
|
||||
|
||||
# roiTakeProfitPercentage is the take profit percentage of the position ROI (currently the price change)
|
||||
# roiTakeProfitPercentage is used to force taking profit percentage of the position ROI (currently the price change)
|
||||
# force to take the profit ROI exceeded the percentage.
|
||||
roiTakeProfitPercentage: 25%
|
||||
|
||||
# lowerShadowRatio is used to force taking profit when the (lower shadow height / low price) > lowerShadowRatio
|
||||
# roiMinTakeProfitPercentage applies to lowerShadowRatio and cumulatedVolume exit options
|
||||
roiMinTakeProfitPercentage: 10%
|
||||
|
||||
# lowerShadowRatio is used to taking profit when the (lower shadow height / low price) > lowerShadowRatio
|
||||
# you can grab a simple stats by the following SQL:
|
||||
# SELECT ((close - low) / close) AS shadow_ratio FROM binance_klines WHERE symbol = 'ETHUSDT' AND `interval` = '5m' AND start_time > '2022-01-01' ORDER BY shadow_ratio DESC LIMIT 20;
|
||||
lowerShadowRatio: 3%
|
||||
|
||||
# cumulatedVolume is used to take profit when the cumulated quote volume from the klines exceeded a threshold
|
||||
cumulatedVolume:
|
||||
minVolume: 50_000
|
||||
enabled: true
|
||||
minQuoteVolume: 90_000_000
|
||||
window: 5
|
||||
|
||||
marginOrderSideEffect: repay
|
||||
|
@ -52,7 +57,7 @@ exchangeStrategies:
|
|||
backtest:
|
||||
sessions:
|
||||
- binance
|
||||
startTime: "2022-04-01"
|
||||
startTime: "2022-01-01"
|
||||
endTime: "2022-06-08"
|
||||
symbols:
|
||||
- ETHUSDT
|
||||
|
|
|
@ -1,26 +1,41 @@
|
|||
# usage:
|
||||
#
|
||||
# go run ./cmd/bbgo optimize --config bollmaker_ethusdt.yaml --optimizer-config optimizer.yaml --debug
|
||||
# go run ./cmd/bbgo optimize --config config/pivotshort-ETHUSDT.yaml --optimizer-config config/pivotshort_optimizer.yaml --debug
|
||||
#
|
||||
---
|
||||
matrix:
|
||||
- type: iterate
|
||||
label: interval
|
||||
path: '/exchangeStrategies/0/pivotshort/interval'
|
||||
values: [ "5m", "30m", "1h" ]
|
||||
|
||||
- type: range
|
||||
path: '/exchangeStrategies/0/pivotshort/pivotLength'
|
||||
label: pivotLength
|
||||
min: 20.0
|
||||
max: 200.0
|
||||
step: 10.0
|
||||
#- type: iterate
|
||||
# label: interval
|
||||
# path: '/exchangeStrategies/0/pivotshort/interval'
|
||||
# values: [ "5m", "30m" ]
|
||||
|
||||
#- type: range
|
||||
# path: '/exchangeStrategies/0/pivotshort/pivotLength'
|
||||
# label: pivotLength
|
||||
# min: 100.0
|
||||
# max: 200.0
|
||||
# step: 20.0
|
||||
|
||||
- type: range
|
||||
path: '/exchangeStrategies/0/pivotshort/breakLow/stopEMARange'
|
||||
label: pivotLength
|
||||
label: stopEMARange
|
||||
min: 0%
|
||||
max: 10%
|
||||
step: 1%
|
||||
|
||||
- type: range
|
||||
path: '/exchangeStrategies/0/pivotshort/exit/roiStopLossPercentage'
|
||||
label: roiStopLossPercentage
|
||||
min: 0.5%
|
||||
max: 3%
|
||||
step: 0.5%
|
||||
|
||||
- type: range
|
||||
path: '/exchangeStrategies/0/pivotshort/exit/roiTakeProfitPercentage'
|
||||
label: roiTakeProfitPercentage
|
||||
min: 10%
|
||||
max: 50%
|
||||
step: 5%
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package pivotshort
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -39,7 +40,11 @@ func (s *TradeStats) Add(pnl fixedpoint.Value) {
|
|||
s.MostLossTrade = fixedpoint.Min(s.MostLossTrade, pnl)
|
||||
}
|
||||
|
||||
s.WinningRatio = fixedpoint.NewFromFloat(float64(s.NumOfProfitTrade) / float64(s.NumOfLossTrade))
|
||||
if s.NumOfLossTrade == 0 && s.NumOfProfitTrade > 0 {
|
||||
s.WinningRatio = fixedpoint.One
|
||||
} else {
|
||||
s.WinningRatio = fixedpoint.NewFromFloat(float64(s.NumOfProfitTrade) / float64(s.NumOfLossTrade))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TradeStats) String() string {
|
||||
|
@ -76,12 +81,21 @@ type Entry struct {
|
|||
MarginSideEffect types.MarginOrderSideEffectType `json:"marginOrderSideEffect"`
|
||||
}
|
||||
|
||||
type CumulatedVolume struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
MinQuoteVolume fixedpoint.Value `json:"minQuoteVolume"`
|
||||
Window int `json:"window"`
|
||||
}
|
||||
|
||||
type Exit struct {
|
||||
RoiStopLossPercentage fixedpoint.Value `json:"roiStopLossPercentage"`
|
||||
RoiTakeProfitPercentage fixedpoint.Value `json:"roiTakeProfitPercentage"`
|
||||
RoiStopLossPercentage fixedpoint.Value `json:"roiStopLossPercentage"`
|
||||
RoiTakeProfitPercentage fixedpoint.Value `json:"roiTakeProfitPercentage"`
|
||||
RoiMinTakeProfitPercentage fixedpoint.Value `json:"roiMinTakeProfitPercentage"`
|
||||
|
||||
LowerShadowRatio fixedpoint.Value `json:"lowerShadowRatio"`
|
||||
|
||||
CumulatedVolume *CumulatedVolume `json:"cumulatedVolume"`
|
||||
|
||||
MarginSideEffect types.MarginOrderSideEffectType `json:"marginOrderSideEffect"`
|
||||
}
|
||||
|
||||
|
@ -287,36 +301,47 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
// TODO: apply quantity to this formula
|
||||
roi := s.Position.AverageCost.Sub(kline.Close).Div(s.Position.AverageCost)
|
||||
if roi.Compare(s.Exit.RoiStopLossPercentage.Neg()) < 0 {
|
||||
// SL
|
||||
s.Notify("%s ROI StopLoss triggered at price %f, ROI = %s", s.Symbol, kline.Close.Float64(), roi.Percentage())
|
||||
if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil {
|
||||
log.WithError(err).Errorf("graceful cancel order error")
|
||||
}
|
||||
|
||||
if err := s.ClosePosition(ctx, fixedpoint.One); err != nil {
|
||||
log.WithError(err).Errorf("close position error")
|
||||
}
|
||||
|
||||
// stop loss
|
||||
s.Notify("%s ROI StopLoss triggered at price %f: Loss %s", s.Symbol, kline.Close.Float64(), roi.Percentage())
|
||||
s.closePosition(ctx)
|
||||
return
|
||||
} else if roi.Compare(s.Exit.RoiTakeProfitPercentage) > 0 { // disable this condition temporarily
|
||||
s.Notify("%s TakeProfit triggered at price %f, ROI take profit percentage by %s", s.Symbol, kline.Close.Float64(), roi.Percentage(), kline)
|
||||
if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil {
|
||||
log.WithError(err).Errorf("graceful cancel order error")
|
||||
}
|
||||
} else {
|
||||
// take profit
|
||||
if roi.Compare(s.Exit.RoiTakeProfitPercentage) > 0 { // force take profit
|
||||
s.Notify("%s TakeProfit triggered at price %f: by ROI percentage %s", s.Symbol, kline.Close.Float64(), roi.Percentage(), kline)
|
||||
s.closePosition(ctx)
|
||||
return
|
||||
} else if !s.Exit.RoiMinTakeProfitPercentage.IsZero() && roi.Compare(s.Exit.RoiMinTakeProfitPercentage) > 0 {
|
||||
if !s.Exit.LowerShadowRatio.IsZero() && kline.GetLowerShadowHeight().Div(kline.Close).Compare(s.Exit.LowerShadowRatio) > 0 {
|
||||
s.Notify("%s TakeProfit triggered at price %f: by shadow ratio %f",
|
||||
s.Symbol,
|
||||
kline.Close.Float64(),
|
||||
kline.GetLowerShadowRatio().Float64(), kline)
|
||||
s.closePosition(ctx)
|
||||
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 err := s.ClosePosition(ctx, fixedpoint.One); err != nil {
|
||||
log.WithError(err).Errorf("close position error")
|
||||
if cqv.Compare(s.Exit.CumulatedVolume.MinQuoteVolume) > 0 {
|
||||
s.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)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !s.Exit.LowerShadowRatio.IsZero() && kline.GetLowerShadowHeight().Div(kline.Close).Compare(s.Exit.LowerShadowRatio) > 0 {
|
||||
s.Notify("%s TakeProfit triggered at price %f: shadow ratio %f", s.Symbol, kline.Close.Float64(), kline.GetLowerShadowRatio().Float64(), kline)
|
||||
if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil {
|
||||
log.WithError(err).Errorf("graceful cancel order error")
|
||||
}
|
||||
|
||||
if err := s.ClosePosition(ctx, fixedpoint.One); err != nil {
|
||||
log.WithError(err).Errorf("close position error")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,13 +404,23 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
})
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
log.Info(s.TradeStats.String())
|
||||
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
|
||||
wg.Done()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) closePosition(ctx context.Context) {
|
||||
if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil {
|
||||
log.WithError(err).Errorf("graceful cancel order error")
|
||||
}
|
||||
|
||||
if err := s.ClosePosition(ctx, fixedpoint.One); err != nil {
|
||||
log.WithError(err).Errorf("close position error")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) findHigherPivotLow(price fixedpoint.Value) (fixedpoint.Value, bool) {
|
||||
for l := len(s.pivotLowPrices) - 1; l > 0; l-- {
|
||||
if s.pivotLowPrices[l].Compare(price) > 0 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user