Skip to content

Commit

Permalink
Merge pull request c9s#799 from andycheng123/improve/supertrend-strategy
Browse files Browse the repository at this point in the history
Improve supertrend strategy
  • Loading branch information
andycheng123 authored Jul 7, 2022
2 parents e778db1 + f877775 commit 72f18c1
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 111 deletions.
32 changes: 24 additions & 8 deletions config/supertrend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ backtest:
# see here for more details
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
startTime: "2022-01-01"
endTime: "2022-06-18"
endTime: "2022-06-30"
symbols:
- BTCUSDT
accounts:
Expand All @@ -36,7 +36,7 @@ exchangeStrategies:
symbol: BTCUSDT

# interval is how long do you want to update your order price and quantity
interval: 1h
interval: 5m

# leverage is the leverage of the orders
leverage: 1.0
Expand All @@ -48,15 +48,31 @@ exchangeStrategies:
# Supertrend indicator parameters
superTrend:
# ATR window used by Supertrend
averageTrueRangeWindow: 39
averageTrueRangeWindow: 49
# ATR Multiplier for calculating super trend prices, the higher, the stronger the trends are
averageTrueRangeMultiplier: 3
averageTrueRangeMultiplier: 4

# Use linear regression as trend confirmation
linearRegression:
interval: 5m
window: 80

# TP according to ATR multiple, 0 to disable this
takeProfitMultiplier: 3
TakeProfitAtrMultiplier: 0

# Set SL price to the low of the triggering Kline
stopLossByTriggeringK: true
stopLossByTriggeringK: false

# TP/SL by reversed supertrend signal
stopByReversedSupertrend: false

# TP/SL by reversed DEMA signal
stopByReversedDema: false

# TP/SL by reversed linear regression signal
stopByReversedLinGre: false

# TP/SL by reversed signals
tpslBySignal: true
exits:
# roiStopLoss is the stop loss percentage of the position ROI (currently the price change)
- roiStopLoss:
percentage: 4%
23 changes: 19 additions & 4 deletions doc/strategy/supertrend.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,27 @@ Supertrend strategy needs margin enabled in order to submit short orders, and yo
- The MA window of the ATR indicator used by Supertrend.
- `averageTrueRangeMultiplier`
- Multiplier for calculating upper and lower bond prices, the higher, the stronger the trends are, but also makes it less sensitive.
- `takeProfitMultiplier`
- `linearRegression`
- Use linear regression as trend confirmation
- `interval`
- Time interval of linear regression
- `window`
- Window of linear regression
- `takeProfitAtrMultiplier`
- TP according to ATR multiple, 0 to disable this.
- `stopLossByTriggeringK`
- Set SL price to the low of the triggering Kline.
- `tpslBySignal`
- TP/SL by reversed signals.
- Set SL price to the low/high of the triggering Kline.
- `stopByReversedSupertrend`
- TP/SL by reversed supertrend signal.
- `stopByReversedDema`
- TP/SL by reversed DEMA signal.
- `stopByReversedLinGre`
- TP/SL by reversed linear regression signal.
- `exits`
- Exit methods to TP/SL
- `roiStopLoss`
- The stop loss percentage of the position ROI (currently the price change)
- `percentage`


#### Examples
Expand Down
65 changes: 65 additions & 0 deletions pkg/strategy/supertrend/double_dema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package supertrend

import (
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
)

type DoubleDema struct {
Interval types.Interval `json:"interval"`

// FastDEMAWindow DEMA window for checking breakout
FastDEMAWindow int `json:"fastDEMAWindow"`
// SlowDEMAWindow DEMA window for checking breakout
SlowDEMAWindow int `json:"slowDEMAWindow"`
fastDEMA *indicator.DEMA
slowDEMA *indicator.DEMA
}

// getDemaSignal get current DEMA signal
func (dd *DoubleDema) getDemaSignal(openPrice float64, closePrice float64) types.Direction {
var demaSignal types.Direction = types.DirectionNone

if closePrice > dd.fastDEMA.Last() && closePrice > dd.slowDEMA.Last() && !(openPrice > dd.fastDEMA.Last() && openPrice > dd.slowDEMA.Last()) {
demaSignal = types.DirectionUp
} else if closePrice < dd.fastDEMA.Last() && closePrice < dd.slowDEMA.Last() && !(openPrice < dd.fastDEMA.Last() && openPrice < dd.slowDEMA.Last()) {
demaSignal = types.DirectionDown
}

return demaSignal
}

// preloadDema preloads DEMA indicators
func (dd *DoubleDema) preloadDema(kLineStore *bbgo.MarketDataStore) {
if klines, ok := kLineStore.KLinesOfInterval(dd.fastDEMA.Interval); ok {
for i := 0; i < len(*klines); i++ {
dd.fastDEMA.Update((*klines)[i].GetClose().Float64())
}
}
if klines, ok := kLineStore.KLinesOfInterval(dd.slowDEMA.Interval); ok {
for i := 0; i < len(*klines); i++ {
dd.slowDEMA.Update((*klines)[i].GetClose().Float64())
}
}
}

// setupDoubleDema initializes double DEMA indicators
func (dd *DoubleDema) setupDoubleDema(kLineStore *bbgo.MarketDataStore, interval types.Interval) {
dd.Interval = interval

// DEMA
if dd.FastDEMAWindow == 0 {
dd.FastDEMAWindow = 144
}
dd.fastDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: dd.Interval, Window: dd.FastDEMAWindow}}
dd.fastDEMA.Bind(kLineStore)

if dd.SlowDEMAWindow == 0 {
dd.SlowDEMAWindow = 169
}
dd.slowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: dd.Interval, Window: dd.SlowDEMAWindow}}
dd.slowDEMA.Bind(kLineStore)

dd.preloadDema(kLineStore)
}
73 changes: 73 additions & 0 deletions pkg/strategy/supertrend/lingre.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package supertrend

import (
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
)

// LinGre is Linear Regression baseline
type LinGre struct {
types.IntervalWindow
baseLineSlope float64
}

// Update Linear Regression baseline slope
func (lg *LinGre) Update(klines []types.KLine) {
if len(klines) < lg.Window {
lg.baseLineSlope = 0
return
}

var sumX, sumY, sumXSqr, sumXY float64 = 0, 0, 0, 0
end := len(klines) - 1 // The last kline
for i := end; i >= end-lg.Window+1; i-- {
val := klines[i].GetClose().Float64()
per := float64(end - i + 1)
sumX += per
sumY += val
sumXSqr += per * per
sumXY += val * per
}
length := float64(lg.Window)
slope := (length*sumXY - sumX*sumY) / (length*sumXSqr - sumX*sumX)
average := sumY / length
endPrice := average - slope*sumX/length + slope
startPrice := endPrice + slope*(length-1)
lg.baseLineSlope = (length - 1) / (endPrice - startPrice)

log.Debugf("linear regression baseline slope: %f", lg.baseLineSlope)
}

func (lg *LinGre) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if lg.Interval != interval {
return
}

lg.Update(window)
}

func (lg *LinGre) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(lg.handleKLineWindowUpdate)
}

// GetSignal get linear regression signal
func (lg *LinGre) GetSignal() types.Direction {
var lgSignal types.Direction = types.DirectionNone

switch {
case lg.baseLineSlope > 0:
lgSignal = types.DirectionUp
case lg.baseLineSlope < 0:
lgSignal = types.DirectionDown
}

return lgSignal
}

// preloadLinGre preloads linear regression indicator
func (lg *LinGre) preload(kLineStore *bbgo.MarketDataStore) {
if klines, ok := kLineStore.KLinesOfInterval(lg.Interval); ok {
lg.Update((*klines)[0:])
}
}
Loading

0 comments on commit 72f18c1

Please sign in to comment.