mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-14 02:53:50 +00:00
Merge pull request #799 from andycheng123/improve/supertrend-strategy
Improve supertrend strategy
This commit is contained in:
commit
72f18c1057
|
@ -19,7 +19,7 @@ backtest:
|
||||||
# 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-01-01"
|
||||||
endTime: "2022-06-18"
|
endTime: "2022-06-30"
|
||||||
symbols:
|
symbols:
|
||||||
- BTCUSDT
|
- BTCUSDT
|
||||||
accounts:
|
accounts:
|
||||||
|
@ -36,7 +36,7 @@ exchangeStrategies:
|
||||||
symbol: BTCUSDT
|
symbol: BTCUSDT
|
||||||
|
|
||||||
# interval is how long do you want to update your order price and quantity
|
# 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 is the leverage of the orders
|
||||||
leverage: 1.0
|
leverage: 1.0
|
||||||
|
@ -48,15 +48,31 @@ exchangeStrategies:
|
||||||
# Supertrend indicator parameters
|
# Supertrend indicator parameters
|
||||||
superTrend:
|
superTrend:
|
||||||
# ATR window used by Supertrend
|
# ATR window used by Supertrend
|
||||||
averageTrueRangeWindow: 39
|
averageTrueRangeWindow: 49
|
||||||
# ATR Multiplier for calculating super trend prices, the higher, the stronger the trends are
|
# 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
|
# TP according to ATR multiple, 0 to disable this
|
||||||
takeProfitMultiplier: 3
|
TakeProfitAtrMultiplier: 0
|
||||||
|
|
||||||
# Set SL price to the low of the triggering Kline
|
# Set SL price to the low of the triggering Kline
|
||||||
stopLossByTriggeringK: true
|
stopLossByTriggeringK: false
|
||||||
|
|
||||||
# TP/SL by reversed signals
|
# TP/SL by reversed supertrend signal
|
||||||
tpslBySignal: true
|
stopByReversedSupertrend: false
|
||||||
|
|
||||||
|
# TP/SL by reversed DEMA signal
|
||||||
|
stopByReversedDema: false
|
||||||
|
|
||||||
|
# TP/SL by reversed linear regression signal
|
||||||
|
stopByReversedLinGre: false
|
||||||
|
|
||||||
|
exits:
|
||||||
|
# roiStopLoss is the stop loss percentage of the position ROI (currently the price change)
|
||||||
|
- roiStopLoss:
|
||||||
|
percentage: 4%
|
||||||
|
|
|
@ -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.
|
- The MA window of the ATR indicator used by Supertrend.
|
||||||
- `averageTrueRangeMultiplier`
|
- `averageTrueRangeMultiplier`
|
||||||
- Multiplier for calculating upper and lower bond prices, the higher, the stronger the trends are, but also makes it less sensitive.
|
- 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.
|
- TP according to ATR multiple, 0 to disable this.
|
||||||
- `stopLossByTriggeringK`
|
- `stopLossByTriggeringK`
|
||||||
- Set SL price to the low of the triggering Kline.
|
- Set SL price to the low/high of the triggering Kline.
|
||||||
- `tpslBySignal`
|
- `stopByReversedSupertrend`
|
||||||
- TP/SL by reversed signals.
|
- 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
|
#### Examples
|
||||||
|
|
65
pkg/strategy/supertrend/double_dema.go
Normal file
65
pkg/strategy/supertrend/double_dema.go
Normal file
|
@ -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
pkg/strategy/supertrend/lingre.go
Normal file
73
pkg/strategy/supertrend/lingre.go
Normal file
|
@ -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:])
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package supertrend
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
|
@ -22,6 +23,8 @@ const stateKey = "state-v1"
|
||||||
|
|
||||||
var log = logrus.WithField("strategy", ID)
|
var log = logrus.WithField("strategy", ID)
|
||||||
|
|
||||||
|
// TODO: limit order for ATR TP
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Register the pointer of the strategy struct,
|
// Register the pointer of the strategy struct,
|
||||||
// so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON)
|
// so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON)
|
||||||
|
@ -37,8 +40,9 @@ type Strategy struct {
|
||||||
Market types.Market
|
Market types.Market
|
||||||
|
|
||||||
// persistence fields
|
// persistence fields
|
||||||
Position *types.Position `json:"position,omitempty" persistence:"position"`
|
Position *types.Position `persistence:"position"`
|
||||||
ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"`
|
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
||||||
|
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||||
|
|
||||||
// Order and trade
|
// Order and trade
|
||||||
orderExecutor *bbgo.GeneralOrderExecutor
|
orderExecutor *bbgo.GeneralOrderExecutor
|
||||||
|
@ -51,16 +55,12 @@ type Strategy struct {
|
||||||
// Symbol is the market symbol you want to trade
|
// Symbol is the market symbol you want to trade
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
|
|
||||||
|
// Double DEMA
|
||||||
|
DoubleDema
|
||||||
|
|
||||||
// Interval is how long do you want to update your order price and quantity
|
// Interval is how long do you want to update your order price and quantity
|
||||||
Interval types.Interval `json:"interval"`
|
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
|
|
||||||
|
|
||||||
// SuperTrend indicator
|
// SuperTrend indicator
|
||||||
// SuperTrend SuperTrend `json:"superTrend"`
|
// SuperTrend SuperTrend `json:"superTrend"`
|
||||||
Supertrend *indicator.Supertrend
|
Supertrend *indicator.Supertrend
|
||||||
|
@ -69,21 +69,33 @@ type Strategy struct {
|
||||||
// SupertrendMultiplier ATR multiplier for calculation of supertrend
|
// SupertrendMultiplier ATR multiplier for calculation of supertrend
|
||||||
SupertrendMultiplier float64 `json:"supertrendMultiplier"`
|
SupertrendMultiplier float64 `json:"supertrendMultiplier"`
|
||||||
|
|
||||||
|
// LinearRegression Use linear regression as trend confirmation
|
||||||
|
LinearRegression *LinGre `json:"linearRegression,omitempty"`
|
||||||
|
|
||||||
// Leverage
|
// Leverage
|
||||||
Leverage float64 `json:"leverage"`
|
Leverage float64 `json:"leverage"`
|
||||||
|
|
||||||
// TakeProfitMultiplier TP according to ATR multiple, 0 to disable this
|
// TakeProfitAtrMultiplier TP according to ATR multiple, 0 to disable this
|
||||||
TakeProfitMultiplier float64 `json:"takeProfitMultiplier"`
|
TakeProfitAtrMultiplier float64 `json:"takeProfitAtrMultiplier"`
|
||||||
|
|
||||||
// StopLossByTriggeringK Set SL price to the low of the triggering Kline
|
// StopLossByTriggeringK Set SL price to the low/high of the triggering Kline
|
||||||
StopLossByTriggeringK bool `json:"stopLossByTriggeringK"`
|
StopLossByTriggeringK bool `json:"stopLossByTriggeringK"`
|
||||||
|
|
||||||
// TPSLBySignal TP/SL by reversed signals
|
// StopByReversedSupertrend TP/SL by reversed supertrend signal
|
||||||
TPSLBySignal bool `json:"tpslBySignal"`
|
StopByReversedSupertrend bool `json:"stopByReversedSupertrend"`
|
||||||
|
|
||||||
|
// StopByReversedDema TP/SL by reversed DEMA signal
|
||||||
|
StopByReversedDema bool `json:"stopByReversedDema"`
|
||||||
|
|
||||||
|
// StopByReversedLinGre TP/SL by reversed linear regression signal
|
||||||
|
StopByReversedLinGre bool `json:"stopByReversedLinGre"`
|
||||||
|
|
||||||
currentTakeProfitPrice fixedpoint.Value
|
currentTakeProfitPrice fixedpoint.Value
|
||||||
currentStopLossPrice fixedpoint.Value
|
currentStopLossPrice fixedpoint.Value
|
||||||
|
|
||||||
|
// ExitMethods Exit methods
|
||||||
|
ExitMethods []bbgo.ExitMethod `json:"exits"`
|
||||||
|
|
||||||
// StrategyController
|
// StrategyController
|
||||||
bbgo.StrategyController
|
bbgo.StrategyController
|
||||||
}
|
}
|
||||||
|
@ -141,8 +153,7 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
|
||||||
|
|
||||||
orderForm := s.generateOrderForm(side, quantity, types.SideEffectTypeAutoRepay)
|
orderForm := s.generateOrderForm(side, quantity, types.SideEffectTypeAutoRepay)
|
||||||
|
|
||||||
log.Infof("submit close position order %v", orderForm)
|
bbgo.Notify("submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage, orderForm)
|
||||||
bbgo.Notify("Submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage)
|
|
||||||
|
|
||||||
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -153,18 +164,24 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preloadSupertrend preloads supertrend indicator
|
||||||
|
func preloadSupertrend(supertrend *indicator.Supertrend, kLineStore *bbgo.MarketDataStore) {
|
||||||
|
if klines, ok := kLineStore.KLinesOfInterval(supertrend.Interval); ok {
|
||||||
|
for i := 0; i < len(*klines); i++ {
|
||||||
|
supertrend.Update((*klines)[i].GetHigh().Float64(), (*klines)[i].GetLow().Float64(), (*klines)[i].GetClose().Float64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// setupIndicators initializes indicators
|
// setupIndicators initializes indicators
|
||||||
func (s *Strategy) setupIndicators() {
|
func (s *Strategy) setupIndicators() {
|
||||||
if s.FastDEMAWindow == 0 {
|
// K-line store for indicators
|
||||||
s.FastDEMAWindow = 144
|
kLineStore, _ := s.session.MarketDataStore(s.Symbol)
|
||||||
}
|
|
||||||
s.fastDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FastDEMAWindow}}
|
|
||||||
|
|
||||||
if s.SlowDEMAWindow == 0 {
|
// Double DEMA
|
||||||
s.SlowDEMAWindow = 169
|
s.setupDoubleDema(kLineStore, s.Interval)
|
||||||
}
|
|
||||||
s.slowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SlowDEMAWindow}}
|
|
||||||
|
|
||||||
|
// Supertrend
|
||||||
if s.SupertrendWindow == 0 {
|
if s.SupertrendWindow == 0 {
|
||||||
s.SupertrendWindow = 39
|
s.SupertrendWindow = 39
|
||||||
}
|
}
|
||||||
|
@ -173,23 +190,62 @@ func (s *Strategy) setupIndicators() {
|
||||||
}
|
}
|
||||||
s.Supertrend = &indicator.Supertrend{IntervalWindow: types.IntervalWindow{Window: s.SupertrendWindow, Interval: s.Interval}, ATRMultiplier: s.SupertrendMultiplier}
|
s.Supertrend = &indicator.Supertrend{IntervalWindow: types.IntervalWindow{Window: s.SupertrendWindow, Interval: s.Interval}, ATRMultiplier: s.SupertrendMultiplier}
|
||||||
s.Supertrend.AverageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.SupertrendWindow, Interval: s.Interval}}
|
s.Supertrend.AverageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.SupertrendWindow, Interval: s.Interval}}
|
||||||
|
s.Supertrend.Bind(kLineStore)
|
||||||
|
preloadSupertrend(s.Supertrend, kLineStore)
|
||||||
|
|
||||||
|
// Linear Regression
|
||||||
|
if s.LinearRegression != nil {
|
||||||
|
if s.LinearRegression.Window == 0 {
|
||||||
|
s.LinearRegression = nil
|
||||||
|
} else if s.LinearRegression.Interval == "" {
|
||||||
|
s.LinearRegression = nil
|
||||||
|
} else {
|
||||||
|
s.LinearRegression.Bind(kLineStore)
|
||||||
|
s.LinearRegression.preload(kLineStore)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateIndicators updates indicators
|
func (s *Strategy) shouldStop(kline types.KLine, stSignal types.Direction, demaSignal types.Direction, lgSignal types.Direction) bool {
|
||||||
func (s *Strategy) updateIndicators(kline types.KLine) {
|
stopNow := false
|
||||||
closePrice := kline.GetClose().Float64()
|
base := s.Position.GetBase()
|
||||||
|
baseSign := base.Sign()
|
||||||
|
|
||||||
// Update indicators
|
if s.StopLossByTriggeringK && !s.currentStopLossPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentStopLossPrice) > 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentStopLossPrice) < 0)) {
|
||||||
if kline.Interval == s.fastDEMA.Interval {
|
// SL by triggering Kline low/high
|
||||||
s.fastDEMA.Update(closePrice)
|
bbgo.Notify("%s stop loss by triggering the kline low/high", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
|
} else if s.TakeProfitAtrMultiplier > 0 && !s.currentTakeProfitPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) < 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) > 0)) {
|
||||||
|
// TP by multiple of ATR
|
||||||
|
bbgo.Notify("%s take profit by multiple of ATR", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
|
} else if s.StopByReversedSupertrend && ((baseSign < 0 && stSignal == types.DirectionUp) || (baseSign > 0 && stSignal == types.DirectionDown)) {
|
||||||
|
// Use supertrend signal to TP/SL
|
||||||
|
bbgo.Notify("%s stop by the reversed signal of Supertrend", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
|
} else if s.StopByReversedDema && ((baseSign < 0 && demaSignal == types.DirectionUp) || (baseSign > 0 && demaSignal == types.DirectionDown)) {
|
||||||
|
// Use DEMA signal to TP/SL
|
||||||
|
bbgo.Notify("%s stop by the reversed signal of DEMA", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
|
} else if s.StopByReversedLinGre && ((baseSign < 0 && lgSignal == types.DirectionUp) || (baseSign > 0 && lgSignal == types.DirectionDown)) {
|
||||||
|
// Use linear regression signal to TP/SL
|
||||||
|
bbgo.Notify("%s stop by the reversed signal of linear regression", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
}
|
}
|
||||||
if kline.Interval == s.slowDEMA.Interval {
|
|
||||||
s.slowDEMA.Update(closePrice)
|
return stopNow
|
||||||
}
|
}
|
||||||
if kline.Interval == s.Supertrend.Interval {
|
|
||||||
s.Supertrend.Update(kline.GetHigh().Float64(), kline.GetLow().Float64(), closePrice)
|
func (s *Strategy) getSide(stSignal types.Direction, demaSignal types.Direction, lgSignal types.Direction) types.SideType {
|
||||||
|
var side types.SideType
|
||||||
|
|
||||||
|
if stSignal == types.DirectionUp && demaSignal == types.DirectionUp && (s.LinearRegression == nil || lgSignal == types.DirectionUp) {
|
||||||
|
side = types.SideTypeBuy
|
||||||
|
} else if stSignal == types.DirectionDown && demaSignal == types.DirectionDown && (s.LinearRegression == nil || lgSignal == types.DirectionDown) {
|
||||||
|
side = types.SideTypeSell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return side
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) generateOrderForm(side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType) types.SubmitOrder {
|
func (s *Strategy) generateOrderForm(side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType) types.SubmitOrder {
|
||||||
|
@ -235,6 +291,16 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.Position.Strategy = ID
|
s.Position.Strategy = ID
|
||||||
s.Position.StrategyInstanceID = s.InstanceID()
|
s.Position.StrategyInstanceID = s.InstanceID()
|
||||||
|
|
||||||
|
// Profit stats
|
||||||
|
if s.ProfitStats == nil {
|
||||||
|
s.ProfitStats = types.NewProfitStats(s.Market)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trade stats
|
||||||
|
if s.TradeStats == nil {
|
||||||
|
s.TradeStats = &types.TradeStats{}
|
||||||
|
}
|
||||||
|
|
||||||
// Set fee rate
|
// Set fee rate
|
||||||
if s.session.MakerFeeRate.Sign() > 0 || s.session.TakerFeeRate.Sign() > 0 {
|
if s.session.MakerFeeRate.Sign() > 0 || s.session.TakerFeeRate.Sign() > 0 {
|
||||||
s.Position.SetExchangeFeeRate(s.session.ExchangeName, types.ExchangeFee{
|
s.Position.SetExchangeFeeRate(s.session.ExchangeName, types.ExchangeFee{
|
||||||
|
@ -243,15 +309,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profit
|
|
||||||
if s.ProfitStats == nil {
|
|
||||||
s.ProfitStats = types.NewProfitStats(s.Market)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup order executor
|
// Setup order executor
|
||||||
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||||
s.orderExecutor.BindEnvironment(s.Environment)
|
s.orderExecutor.BindEnvironment(s.Environment)
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
|
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
|
|
||||||
// Sync position to redis on trade
|
// Sync position to redis on trade
|
||||||
|
@ -278,6 +340,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
// Setup indicators
|
// Setup indicators
|
||||||
s.setupIndicators()
|
s.setupIndicators()
|
||||||
|
|
||||||
|
// Exit methods
|
||||||
|
for _, method := range s.ExitMethods {
|
||||||
|
method.Bind(session, s.orderExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
s.currentStopLossPrice = fixedpoint.Zero
|
s.currentStopLossPrice = fixedpoint.Zero
|
||||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
s.currentTakeProfitPrice = fixedpoint.Zero
|
||||||
|
|
||||||
|
@ -292,94 +359,66 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update indicators
|
closePrice := kline.GetClose()
|
||||||
s.updateIndicators(kline)
|
openPrice := kline.GetOpen()
|
||||||
|
closePrice64 := closePrice.Float64()
|
||||||
|
openPrice64 := openPrice.Float64()
|
||||||
|
|
||||||
// Get signals
|
// Supertrend signal
|
||||||
closePrice := kline.GetClose().Float64()
|
|
||||||
openPrice := kline.GetOpen().Float64()
|
|
||||||
stSignal := s.Supertrend.GetSignal()
|
stSignal := s.Supertrend.GetSignal()
|
||||||
var demaSignal types.Direction
|
|
||||||
if closePrice > s.fastDEMA.Last() && closePrice > s.slowDEMA.Last() && !(openPrice > s.fastDEMA.Last() && openPrice > s.slowDEMA.Last()) {
|
// DEMA signal
|
||||||
demaSignal = types.DirectionUp
|
demaSignal := s.getDemaSignal(openPrice64, closePrice64)
|
||||||
} else if closePrice < s.fastDEMA.Last() && closePrice < s.slowDEMA.Last() && !(openPrice < s.fastDEMA.Last() && openPrice < s.slowDEMA.Last()) {
|
|
||||||
demaSignal = types.DirectionDown
|
// Linear Regression signal
|
||||||
} else {
|
var lgSignal types.Direction
|
||||||
demaSignal = types.DirectionNone
|
if s.LinearRegression != nil {
|
||||||
|
lgSignal = s.LinearRegression.GetSignal()
|
||||||
}
|
}
|
||||||
|
|
||||||
base := s.Position.GetBase()
|
// TP/SL if there's non-dust position and meets the criteria
|
||||||
baseSign := base.Sign()
|
if !s.Market.IsDustQuantity(s.Position.GetBase().Abs(), closePrice) && s.shouldStop(kline, stSignal, demaSignal, lgSignal) {
|
||||||
|
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
||||||
|
s.currentStopLossPrice = fixedpoint.Zero
|
||||||
|
s.currentTakeProfitPrice = fixedpoint.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TP/SL if there's non-dust position
|
// Get order side
|
||||||
if !s.Market.IsDustQuantity(base.Abs(), kline.GetClose()) {
|
side := s.getSide(stSignal, demaSignal, lgSignal)
|
||||||
if s.StopLossByTriggeringK && !s.currentStopLossPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentStopLossPrice) > 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentStopLossPrice) < 0)) {
|
// Set TP/SL price if needed
|
||||||
// SL by triggering Kline low
|
if side == types.SideTypeBuy {
|
||||||
log.Infof("%s SL by triggering Kline low", s.Symbol)
|
if s.StopLossByTriggeringK {
|
||||||
bbgo.Notify("%s StopLoss by triggering the kline low", s.Symbol)
|
s.currentStopLossPrice = kline.GetLow()
|
||||||
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
}
|
||||||
s.currentStopLossPrice = fixedpoint.Zero
|
if s.TakeProfitAtrMultiplier > 0 {
|
||||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
s.currentTakeProfitPrice = closePrice.Add(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitAtrMultiplier))
|
||||||
}
|
}
|
||||||
} else if s.TakeProfitMultiplier > 0 && !s.currentTakeProfitPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) < 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) > 0)) {
|
} else if side == types.SideTypeSell {
|
||||||
// TP by multiple of ATR
|
if s.StopLossByTriggeringK {
|
||||||
log.Infof("%s TP by multiple of ATR", s.Symbol)
|
s.currentStopLossPrice = kline.GetHigh()
|
||||||
bbgo.Notify("%s TakeProfit by multiple of ATR", s.Symbol)
|
}
|
||||||
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
if s.TakeProfitAtrMultiplier > 0 {
|
||||||
s.currentStopLossPrice = fixedpoint.Zero
|
s.currentTakeProfitPrice = closePrice.Sub(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitAtrMultiplier))
|
||||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
|
||||||
}
|
|
||||||
} else if s.TPSLBySignal {
|
|
||||||
// Use signals to TP/SL
|
|
||||||
log.Infof("%s TP/SL by reverse of DEMA or Supertrend", s.Symbol)
|
|
||||||
bbgo.Notify("%s TP/SL by reverse of DEMA or Supertrend", s.Symbol)
|
|
||||||
if (baseSign < 0 && (stSignal == types.DirectionUp || demaSignal == types.DirectionUp)) || (baseSign > 0 && (stSignal == types.DirectionDown || demaSignal == types.DirectionDown)) {
|
|
||||||
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
|
||||||
s.currentStopLossPrice = fixedpoint.Zero
|
|
||||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open position
|
// Open position
|
||||||
var side types.SideType
|
|
||||||
if stSignal == types.DirectionUp && demaSignal == types.DirectionUp {
|
|
||||||
side = types.SideTypeBuy
|
|
||||||
if s.StopLossByTriggeringK {
|
|
||||||
s.currentStopLossPrice = kline.GetLow()
|
|
||||||
}
|
|
||||||
if s.TakeProfitMultiplier > 0 {
|
|
||||||
s.currentTakeProfitPrice = kline.GetClose().Add(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitMultiplier))
|
|
||||||
}
|
|
||||||
} else if stSignal == types.DirectionDown && demaSignal == types.DirectionDown {
|
|
||||||
side = types.SideTypeSell
|
|
||||||
if s.StopLossByTriggeringK {
|
|
||||||
s.currentStopLossPrice = kline.GetHigh()
|
|
||||||
}
|
|
||||||
if s.TakeProfitMultiplier > 0 {
|
|
||||||
s.currentTakeProfitPrice = kline.GetClose().Sub(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitMultiplier))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The default value of side is an empty string. Unless side is set by the checks above, the result of the following condition is false
|
// The default value of side is an empty string. Unless side is set by the checks above, the result of the following condition is false
|
||||||
if side == types.SideTypeSell || side == types.SideTypeBuy {
|
if side == types.SideTypeSell || side == types.SideTypeBuy {
|
||||||
log.Infof("open %s position for signal %v", s.Symbol, side)
|
|
||||||
bbgo.Notify("open %s position for signal %v", s.Symbol, side)
|
bbgo.Notify("open %s position for signal %v", s.Symbol, side)
|
||||||
// Close opposite position if any
|
// Close opposite position if any
|
||||||
if !s.Position.IsDust(kline.GetClose()) {
|
if !s.Position.IsDust(closePrice) {
|
||||||
if (side == types.SideTypeSell && s.Position.IsLong()) || (side == types.SideTypeBuy && s.Position.IsShort()) {
|
if (side == types.SideTypeSell && s.Position.IsLong()) || (side == types.SideTypeBuy && s.Position.IsShort()) {
|
||||||
log.Infof("close existing %s position before open a new position", s.Symbol)
|
|
||||||
bbgo.Notify("close existing %s position before open a new position", s.Symbol)
|
bbgo.Notify("close existing %s position before open a new position", s.Symbol)
|
||||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("existing %s position has the same direction with the signal", s.Symbol)
|
|
||||||
bbgo.Notify("existing %s position has the same direction with the signal", s.Symbol)
|
bbgo.Notify("existing %s position has the same direction with the signal", s.Symbol)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
orderForm := s.generateOrderForm(side, s.calculateQuantity(kline.GetClose()), types.SideEffectTypeMarginBuy)
|
orderForm := s.generateOrderForm(side, s.calculateQuantity(closePrice), types.SideEffectTypeMarginBuy)
|
||||||
log.Infof("submit open position order %v", orderForm)
|
log.Infof("submit open position order %v", orderForm)
|
||||||
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -395,6 +434,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
close(s.stopC)
|
close(s.stopC)
|
||||||
|
|
||||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in New Issue
Block a user