mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 23:05:15 +00:00
Merge pull request #932 from c9s/feature/bolllmaker-trendEMA
feature: strategy/bolllmaker trend ema
This commit is contained in:
commit
ecdfe8cab6
|
@ -16,8 +16,8 @@ backtest:
|
||||||
# for testing max draw down (MDD) at 03-12
|
# for testing max draw down (MDD) at 03-12
|
||||||
# see here for more details
|
# see here for more details
|
||||||
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
||||||
startTime: "2022-01-01"
|
startTime: "2022-05-01"
|
||||||
endTime: "2022-07-18"
|
endTime: "2022-08-14"
|
||||||
sessions:
|
sessions:
|
||||||
- binance
|
- binance
|
||||||
symbols:
|
symbols:
|
||||||
|
@ -40,6 +40,9 @@ exchangeStrategies:
|
||||||
# quantity is the base order quantity for your buy/sell order.
|
# quantity is the base order quantity for your buy/sell order.
|
||||||
quantity: 0.05
|
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.
|
# 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.
|
# 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.
|
# Turn this on if you want to do real trading.
|
||||||
|
@ -56,13 +59,28 @@ exchangeStrategies:
|
||||||
# For short position, you will only place buy order below the price (= average cost * (1 - minProfitSpread))
|
# For short position, you will only place buy order below the price (= average cost * (1 - minProfitSpread))
|
||||||
minProfitSpread: 0.1%
|
minProfitSpread: 0.1%
|
||||||
|
|
||||||
# EXPERIMENTAL
|
# trendEMA detects the trend by a given EMA
|
||||||
# Dynamic spread is an experimental feature. Use at your own risk!
|
# 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
|
||||||
|
|
||||||
|
# ==================================================================
|
||||||
|
# Dynamic spread is an experimental feature. it will override the fixed spread settings above.
|
||||||
#
|
#
|
||||||
# dynamicSpread enables the automatic adjustment to bid and ask spread.
|
# dynamicSpread enables the automatic adjustment to bid and ask spread.
|
||||||
# Choose one of the scaling strategy to enable dynamicSpread:
|
# Choose one of the scaling strategy to enable dynamicSpread:
|
||||||
# - amplitude: scales by K-line amplitude
|
# - amplitude: scales by K-line amplitude
|
||||||
# - weightedBollWidth: scales by weighted Bollinger band width (explained below)
|
# - weightedBollWidth: scales by weighted Bollinger band width (explained below)
|
||||||
|
# ==================================================================
|
||||||
|
#
|
||||||
|
# =========================================
|
||||||
|
# dynamicSpread with amplitude
|
||||||
|
# =========================================
|
||||||
# dynamicSpread:
|
# dynamicSpread:
|
||||||
# amplitude: # delete other scaling strategy if this is defined
|
# amplitude: # delete other scaling strategy if this is defined
|
||||||
# # window is the window of the SMAs of spreads
|
# # window is the window of the SMAs of spreads
|
||||||
|
@ -85,21 +103,27 @@ exchangeStrategies:
|
||||||
# # when in down band, holds 1.0 by maximum
|
# # when in down band, holds 1.0 by maximum
|
||||||
# # when in up band, holds 0.05 by maximum
|
# # when in up band, holds 0.05 by maximum
|
||||||
# range: [ 0.001, 0.002 ]
|
# 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)))
|
# =========================================
|
||||||
|
# dynamicSpread with weightedBollWidth
|
||||||
|
# =========================================
|
||||||
|
# dynamicSpread:
|
||||||
|
# # 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 .
|
# # A positive number. The greater factor, the sharper weighting function. Default set to 1.0 .
|
||||||
# sensitivity: 1.0
|
# sensitivity: 1.0
|
||||||
#
|
#
|
||||||
|
@ -118,10 +142,10 @@ exchangeStrategies:
|
||||||
# domain: [ 0.1, 0.5 ]
|
# domain: [ 0.1, 0.5 ]
|
||||||
# range: [ 0.001, 0.002 ]
|
# range: [ 0.001, 0.002 ]
|
||||||
|
|
||||||
|
|
||||||
# maxExposurePosition is the maximum position you can hold
|
# 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 long position by maximum
|
||||||
# -10 means you can hold -10 ETH short 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: 3.0
|
||||||
maxExposurePosition: 10
|
maxExposurePosition: 10
|
||||||
|
|
||||||
|
@ -151,6 +175,10 @@ exchangeStrategies:
|
||||||
# downtrendSkew, like the strongDowntrendSkew, but the price is still in the default band.
|
# downtrendSkew, like the strongDowntrendSkew, but the price is still in the default band.
|
||||||
downtrendSkew: 1.2
|
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:
|
defaultBollinger:
|
||||||
interval: "1h"
|
interval: "1h"
|
||||||
window: 21
|
window: 21
|
||||||
|
@ -164,10 +192,10 @@ exchangeStrategies:
|
||||||
bandWidth: 2.0
|
bandWidth: 2.0
|
||||||
|
|
||||||
# tradeInBand: when tradeInBand is set, you will only place orders in the bollinger band.
|
# 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: when this set, it will only place buy order when the current price is below the SMA line.
|
||||||
buyBelowNeutralSMA: false
|
buyBelowNeutralSMA: true
|
||||||
|
|
||||||
exits:
|
exits:
|
||||||
|
|
||||||
|
|
|
@ -271,6 +271,16 @@ func (e *GeneralOrderExecutor) ClosePosition(ctx context.Context, percentage fix
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check base balance and adjust the close position order
|
||||||
|
if e.position.IsLong() {
|
||||||
|
if baseBalance, ok := e.session.Account.Balance(e.position.Market.BaseCurrency); ok {
|
||||||
|
submitOrder.Quantity = fixedpoint.Min(submitOrder.Quantity, baseBalance.Available)
|
||||||
|
}
|
||||||
|
if submitOrder.Quantity.IsZero() {
|
||||||
|
return fmt.Errorf("insufficient base balance, can not sell: %+v", submitOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tagStr := strings.Join(tags, ",")
|
tagStr := strings.Join(tags, ",")
|
||||||
submitOrder.Tag = tagStr
|
submitOrder.Tag = tagStr
|
||||||
|
|
||||||
|
|
6
pkg/strategy/bollmaker/doc.go
Normal file
6
pkg/strategy/bollmaker/doc.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// bollmaker is a maker strategy depends on the bollinger band
|
||||||
|
//
|
||||||
|
// bollmaker uses two bollinger bands for trading:
|
||||||
|
// 1) the first bollinger is a long-term time frame bollinger, it controls your position. (how much you can hold)
|
||||||
|
// 2) the second bollinger is a short-term time frame bollinger, it controls whether places the orders or not.
|
||||||
|
package bollmaker
|
|
@ -58,6 +58,10 @@ type Strategy struct {
|
||||||
|
|
||||||
bbgo.QuantityOrAmount
|
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.
|
// Spread is the price spread from the middle price.
|
||||||
// For ask orders, the ask price is ((bestAsk + bestBid) / 2 * (1.0 + spread))
|
// 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))
|
// 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)
|
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)
|
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
|
canSell := true
|
||||||
canBuy := 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 {
|
if !hasQuoteBalance || buyOrder.Quantity.Mul(buyOrder.Price).Compare(quoteBalance.Available) > 0 {
|
||||||
canBuy = false
|
canBuy = false
|
||||||
}
|
}
|
||||||
|
@ -377,11 +388,13 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
|
||||||
|
|
||||||
if isLongPosition {
|
if isLongPosition {
|
||||||
// for long position if the current price is lower than the minimal profitable price then we should stop sell
|
// 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 {
|
if midPrice.Compare(minProfitPrice) < 0 {
|
||||||
canSell = false
|
canSell = false
|
||||||
}
|
}
|
||||||
} else if isShortPosition {
|
} else if isShortPosition {
|
||||||
// for short position if the current price is higher than the minimal profitable price then we should stop buy
|
// 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 {
|
if midPrice.Compare(minProfitPrice) > 0 {
|
||||||
canBuy = false
|
canBuy = false
|
||||||
}
|
}
|
||||||
|
@ -395,6 +408,14 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
|
||||||
canBuy = false
|
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 {
|
if canSell {
|
||||||
submitOrders = append(submitOrders, sellOrder)
|
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)
|
s.ExitMethods.Bind(session, s.orderExecutor)
|
||||||
|
|
||||||
|
if s.TrendEMA != nil {
|
||||||
|
s.TrendEMA.Bind(session, s.orderExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
if bbgo.IsBackTesting {
|
if bbgo.IsBackTesting {
|
||||||
log.Warn("turning of useTickerPrice option in the back-testing environment...")
|
log.Warn("turning of useTickerPrice option in the back-testing environment...")
|
||||||
s.UseTickerPrice = false
|
s.UseTickerPrice = false
|
||||||
|
|
Loading…
Reference in New Issue
Block a user