From a425c940fa62bf65d873e982fc41fedbbd690523 Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 11 Sep 2022 17:28:54 +0800 Subject: [PATCH] bollmaker: add trendEMA support --- config/bollmaker.yaml | 58 ++++++++++++++++++++---------- pkg/strategy/bollmaker/strategy.go | 25 +++++++++++++ 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/config/bollmaker.yaml b/config/bollmaker.yaml index 07f8099b4..f7a9e1b0b 100644 --- a/config/bollmaker.yaml +++ b/config/bollmaker.yaml @@ -16,8 +16,8 @@ backtest: # for testing max draw down (MDD) at 03-12 # see here for more details # https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp - startTime: "2022-01-01" - endTime: "2022-07-18" + startTime: "2022-05-01" + endTime: "2022-08-14" sessions: - binance symbols: @@ -40,6 +40,9 @@ exchangeStrategies: # quantity is the base order quantity for your buy/sell order. quantity: 0.05 + # amount is used for fixed-amount order, for example, use fixed 20 USDT order for BTCUSDT market + # amount: 20 + # useTickerPrice use the ticker api to get the mid price instead of the closed kline price. # The back-test engine is kline-based, so the ticker price api is not supported. # Turn this on if you want to do real trading. @@ -56,6 +59,17 @@ exchangeStrategies: # For short position, you will only place buy order below the price (= average cost * (1 - minProfitSpread)) minProfitSpread: 0.1% + # trendEMA detects the trend by a given EMA + # when EMA goes up (the last > the previous), allow buy and sell + # when EMA goes down (the last < the previous), disable buy, allow sell + # uncomment this to enable it: + # + trendEMA: + interval: 1d + window: 7 + maxGradient: 1.5 + minGradient: 1.01 + # EXPERIMENTAL # Dynamic spread is an experimental feature. Use at your own risk! # @@ -85,21 +99,23 @@ exchangeStrategies: # # when in down band, holds 1.0 by maximum # # when in up band, holds 0.05 by maximum # range: [ 0.001, 0.002 ] - # weightedBollWidth: # delete other scaling strategy if this is defined - # # Scale spread base on weighted Bollinger band width ratio between default and neutral bands. - # # Given the default band: moving average bd_mid, band from bd_lower to bd_upper. - # # And the neutral band: from bn_lower to bn_upper - # # Set the sigmoid weighting function: - # # - to ask spread, the weighting density function d_weight(x) is sigmoid((x - bd_mid) / (bd_upper - bd_lower)) - # # - to bid spread, the weighting density function d_weight(x) is sigmoid((bd_mid - x) / (bd_upper - bd_lower)) - # # Then calculate the weighted band width ratio by taking integral of d_weight(x) from bx_lower to bx_upper: - # # - weighted_ratio = integral(d_weight from bn_lower to bn_upper) / integral(d_weight from bd_lower to bd_upper) - # # - The wider neutral band get greater ratio - # # - To ask spread, the higher neutral band get greater ratio - # # - To bid spread, the lower neutral band get greater ratio - # # The weighted ratio always positive, and may be greater than 1 if neutral band is wider than default band. # - # # Sensitivity factor of the weighting function: 1 / (1 + exp(-(x - bd_mid) * sensitivity / (bd_upper - bd_lower))) + # # weightedBollWidth scales spread base on weighted Bollinger bandwidth ratio between default and neutral bands. + # # + # # Given the default band: moving average bd_mid, band from bd_lower to bd_upper. + # # And the neutral band: from bn_lower to bn_upper + # # Set the sigmoid weighting function: + # # - to ask spread, the weighting density function d_weight(x) is sigmoid((x - bd_mid) / (bd_upper - bd_lower)) + # # - to bid spread, the weighting density function d_weight(x) is sigmoid((bd_mid - x) / (bd_upper - bd_lower)) + # # Then calculate the weighted band width ratio by taking integral of d_weight(x) from bx_lower to bx_upper: + # # - weighted_ratio = integral(d_weight from bn_lower to bn_upper) / integral(d_weight from bd_lower to bd_upper) + # # - The wider neutral band get greater ratio + # # - To ask spread, the higher neutral band get greater ratio + # # - To bid spread, the lower neutral band get greater ratio + # # The weighted ratio always positive, and may be greater than 1 if neutral band is wider than default band. + # weightedBollWidth: # delete other scaling strategy if this is defined + # + # # sensitivity is a factor of the weighting function: 1 / (1 + exp(-(x - bd_mid) * sensitivity / (bd_upper - bd_lower))) # # A positive number. The greater factor, the sharper weighting function. Default set to 1.0 . # sensitivity: 1.0 # @@ -118,10 +134,10 @@ exchangeStrategies: # domain: [ 0.1, 0.5 ] # range: [ 0.001, 0.002 ] - # maxExposurePosition is the maximum position you can hold # +10 means you can hold 10 ETH long position by maximum # -10 means you can hold -10 ETH short position by maximum + # uncomment this if you want a fixed position exposure. # maxExposurePosition: 3.0 maxExposurePosition: 10 @@ -151,6 +167,10 @@ exchangeStrategies: # downtrendSkew, like the strongDowntrendSkew, but the price is still in the default band. downtrendSkew: 1.2 + # defaultBollinger is a long-term time frame bollinger + # this bollinger band is used for controlling your position (how much you can hold) + # when price is near the upper band, it holds less. + # when price is near the lower band, it holds more. defaultBollinger: interval: "1h" window: 21 @@ -164,10 +184,10 @@ exchangeStrategies: bandWidth: 2.0 # tradeInBand: when tradeInBand is set, you will only place orders in the bollinger band. - tradeInBand: false + tradeInBand: true # buyBelowNeutralSMA: when this set, it will only place buy order when the current price is below the SMA line. - buyBelowNeutralSMA: false + # buyBelowNeutralSMA: false exits: diff --git a/pkg/strategy/bollmaker/strategy.go b/pkg/strategy/bollmaker/strategy.go index f9b3200bd..5be8529b1 100644 --- a/pkg/strategy/bollmaker/strategy.go +++ b/pkg/strategy/bollmaker/strategy.go @@ -58,6 +58,10 @@ type Strategy struct { bbgo.QuantityOrAmount + // TrendEMA is used for detecting the trend by a given EMA + // you can define interval and window + TrendEMA *bbgo.TrendEMA `json:"trendEMA"` + // Spread is the price spread from the middle price. // For ask orders, the ask price is ((bestAsk + bestBid) / 2 * (1.0 + spread)) // For bid orders, the bid price is ((bestAsk + bestBid) / 2 * (1.0 - spread)) @@ -185,6 +189,10 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { }) } + if s.TrendEMA != nil { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.TrendEMA.Interval}) + } + s.ExitMethods.SetAndSubscribe(session, s) } @@ -287,6 +295,8 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k log.Infof("current %s unrealized profit: %f %s", s.Symbol, s.Position.UnrealizedProfit(midPrice).Float64(), s.Market.QuoteCurrency) } + // by default, we turn both sell and buy on, + // which means we will place buy and sell orders canSell := true canBuy := true @@ -360,6 +370,7 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k } + // check balance and switch the orders if !hasQuoteBalance || buyOrder.Quantity.Mul(buyOrder.Price).Compare(quoteBalance.Available) > 0 { canBuy = false } @@ -377,11 +388,13 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k if isLongPosition { // for long position if the current price is lower than the minimal profitable price then we should stop sell + // this avoid loss trade if midPrice.Compare(minProfitPrice) < 0 { canSell = false } } else if isShortPosition { // for short position if the current price is higher than the minimal profitable price then we should stop buy + // this avoid loss trade if midPrice.Compare(minProfitPrice) > 0 { canBuy = false } @@ -395,6 +408,14 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k canBuy = false } + // trend EMA protection + if s.TrendEMA != nil { + if !s.TrendEMA.GradientAllowed() { + log.Infof("trendEMA protection: midPrice price %f, gradient %f, turning buy order off", midPrice.Float64(), s.TrendEMA.Gradient()) + canBuy = false + } + } + if canSell { submitOrders = append(submitOrders, sellOrder) } @@ -499,6 +520,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se }) s.ExitMethods.Bind(session, s.orderExecutor) + if s.TrendEMA != nil { + s.TrendEMA.Bind(session, s.orderExecutor) + } + if bbgo.IsBackTesting { log.Warn("turning of useTickerPrice option in the back-testing environment...") s.UseTickerPrice = false