Merge pull request #700 from c9s/strategy/pivot

pivotshort: add breakLow.bounceRatio option
This commit is contained in:
Yo-An Lin 2022-06-10 12:01:29 +08:00 committed by GitHub
commit a4e3fd5c41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 16 deletions

View File

@ -16,9 +16,26 @@ exchangeStrategies:
# breakLow settings are used for shorting when the current price break the previous low # breakLow settings are used for shorting when the current price break the previous low
breakLow: breakLow:
# ratio is how much the price breaks the previous low to trigger the short.
ratio: 0.1% ratio: 0.1%
# quantity is used for submitting the sell order
# if quantity is not set, all base balance will be used for selling the short.
quantity: 10.0 quantity: 10.0
stopEMARange: 5%
# marketOrder submits the market sell order when the closed price is lower than the previous pivot low.
marketOrder: true
# bounceRatio is used for calculating the price of the limit sell order.
# it's ratio of pivot low bounce when a new pivot low is detected.
# Sometimes when the price breaks the previous low, the price might be pulled back to a higher price.
# The bounceRatio is useful for such case, however, you might also miss the chance to short at the price if there is no pull back.
# Notice: When marketOrder is set, bounceRatio will not be used.
# bounceRatio: 0.1%
# stopEMARange is the price range we allow short.
# Short-allowed price range = [current price] > [EMA] * (1 - [stopEMARange])
stopEMARange: 0%
stopEMA: stopEMA:
interval: 1h interval: 1h
window: 99 window: 99
@ -36,7 +53,7 @@ exchangeStrategies:
# roiTakeProfitPercentage is used to force taking profit by percentage of the position ROI (currently the price change) # roiTakeProfitPercentage is used to force taking profit by percentage of the position ROI (currently the price change)
# force to take the profit ROI exceeded the percentage. # force to take the profit ROI exceeded the percentage.
roiTakeProfitPercentage: 25% roiTakeProfitPercentage: 10%
# roiMinTakeProfitPercentage applies to lowerShadowRatio and cumulatedVolume exit options # roiMinTakeProfitPercentage applies to lowerShadowRatio and cumulatedVolume exit options
roiMinTakeProfitPercentage: 10% roiMinTakeProfitPercentage: 10%

View File

@ -67,6 +67,8 @@ type IntervalWindowSetting struct {
// BreakLow -- when price breaks the previous pivot low, we set a trade entry // BreakLow -- when price breaks the previous pivot low, we set a trade entry
type BreakLow struct { type BreakLow struct {
Ratio fixedpoint.Value `json:"ratio"` Ratio fixedpoint.Value `json:"ratio"`
MarketOrder bool `json:"marketOrder"`
BounceRatio fixedpoint.Value `json:"bounceRatio"`
Quantity fixedpoint.Value `json:"quantity"` Quantity fixedpoint.Value `json:"quantity"`
StopEMARange fixedpoint.Value `json:"stopEMARange"` StopEMARange fixedpoint.Value `json:"stopEMARange"`
StopEMA *types.IntervalWindow `json:"stopEMA"` StopEMA *types.IntervalWindow `json:"stopEMA"`
@ -156,7 +158,7 @@ func (s *Strategy) submitOrders(ctx context.Context, orderExecutor bbgo.OrderExe
s.tradeCollector.Process() s.tradeCollector.Process()
} }
func (s *Strategy) placeMarketSell(ctx context.Context, orderExecutor bbgo.OrderExecutor, quantity fixedpoint.Value) { func (s *Strategy) useQuantityOrBaseBalance(quantity fixedpoint.Value) fixedpoint.Value {
if quantity.IsZero() { if quantity.IsZero() {
if balance, ok := s.session.Account.Balance(s.Market.BaseCurrency); ok { if balance, ok := s.session.Account.Balance(s.Market.BaseCurrency); ok {
s.Notify("sell quantity is not set, submitting sell with all base balance: %s", balance.Available.String()) s.Notify("sell quantity is not set, submitting sell with all base balance: %s", balance.Available.String())
@ -166,18 +168,30 @@ func (s *Strategy) placeMarketSell(ctx context.Context, orderExecutor bbgo.Order
if quantity.IsZero() { if quantity.IsZero() {
log.Errorf("quantity is zero, can not submit sell order, please check settings") log.Errorf("quantity is zero, can not submit sell order, please check settings")
return
} }
submitOrder := types.SubmitOrder{ return quantity
}
func (s *Strategy) placeLimitSell(ctx context.Context, orderExecutor bbgo.OrderExecutor, price, quantity fixedpoint.Value) {
s.submitOrders(ctx, orderExecutor, types.SubmitOrder{
Symbol: s.Symbol,
Price: price,
Side: types.SideTypeSell,
Type: types.OrderTypeLimit,
Quantity: quantity,
MarginSideEffect: types.SideEffectTypeMarginBuy,
})
}
func (s *Strategy) placeMarketSell(ctx context.Context, orderExecutor bbgo.OrderExecutor, quantity fixedpoint.Value) {
s.submitOrders(ctx, orderExecutor, types.SubmitOrder{
Symbol: s.Symbol, Symbol: s.Symbol,
Side: types.SideTypeSell, Side: types.SideTypeSell,
Type: types.OrderTypeMarket, Type: types.OrderTypeMarket,
Quantity: quantity, Quantity: quantity,
MarginSideEffect: types.SideEffectTypeMarginBuy, MarginSideEffect: types.SideEffectTypeMarginBuy,
} })
s.submitOrders(ctx, orderExecutor, submitOrder)
} }
func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error {
@ -379,13 +393,18 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
return return
} }
s.Notify("%s price %f breaks the previous low %f with ratio %f, submitting market sell to open a short position", s.Symbol, kline.Close.Float64(), previousLow.Float64(), s.BreakLow.Ratio.Float64())
if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil { if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil {
log.WithError(err).Errorf("graceful cancel order error") log.WithError(err).Errorf("graceful cancel order error")
} }
s.placeMarketSell(ctx, orderExecutor, s.BreakLow.Quantity) quantity := s.useQuantityOrBaseBalance(s.BreakLow.Quantity)
if s.BreakLow.MarketOrder {
s.Notify("%s price %f breaks the previous low %f with ratio %f, submitting market sell to open a short position", s.Symbol, kline.Close.Float64(), previousLow.Float64(), s.BreakLow.Ratio.Float64())
s.placeMarketSell(ctx, orderExecutor, quantity)
} else {
sellPrice := kline.Close.Mul(fixedpoint.One.Add(s.BreakLow.BounceRatio))
s.placeLimitSell(ctx, orderExecutor, sellPrice, quantity)
}
}) })
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
@ -394,13 +413,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
if s.pivot.LastLow() > 0.0 { if s.pivot.LastLow() > 0.0 {
log.Infof("pivot low detected: %f %s", s.pivot.LastLow(), kline.EndTime.Time()) log.Debugf("pivot low detected: %f %s", s.pivot.LastLow(), kline.EndTime.Time())
lastLow := fixedpoint.NewFromFloat(s.pivot.LastLow()) lastLow := fixedpoint.NewFromFloat(s.pivot.LastLow())
if lastLow.Compare(s.lastLow) != 0 {
s.lastLow = lastLow s.lastLow = lastLow
s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow) s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow)
} }
}
}) })
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) { s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {