Skip to content

Commit

Permalink
Merge pull request c9s#1463 from c9s/c9s/bollmaker-ema-crosssignal
Browse files Browse the repository at this point in the history
  • Loading branch information
c9s authored Dec 22, 2023
2 parents 7f8a331 + 382a789 commit c250fec
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 14 deletions.
11 changes: 10 additions & 1 deletion config/bollmaker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ backtest:
# see here for more details
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
startTime: "2022-05-01"
endTime: "2022-08-14"
endTime: "2023-11-01"
sessions:
- binance
symbols:
Expand Down Expand Up @@ -200,6 +200,15 @@ exchangeStrategies:
# buyBelowNeutralSMA: when this set, it will only place buy order when the current price is below the SMA line.
buyBelowNeutralSMA: true

# emaCross is used for turning buy on/off
# when short term EMA cross fast term EMA, turn on buy,
# otherwise, turn off buy
emaCross:
enabled: false
interval: 1h
fastWindow: 3
slowWindow: 12

exits:

# roiTakeProfit is used to force taking profit by percentage of the position ROI (currently the price change)
Expand Down
62 changes: 49 additions & 13 deletions pkg/strategy/bollmaker/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import (
"math"
"sync"

indicatorv2 "github.com/c9s/bbgo/pkg/indicator/v2"
"github.com/c9s/bbgo/pkg/util"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"

indicatorv2 "github.com/c9s/bbgo/pkg/indicator/v2"

"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
Expand Down Expand Up @@ -46,6 +45,16 @@ type BollingerSetting struct {
BandWidth float64 `json:"bandWidth"`
}

type EMACrossSetting struct {
Enabled bool `json:"enabled"`
Interval types.Interval `json:"interval"`
FastWindow int `json:"fastWindow"`
SlowWindow int `json:"slowWindow"`

fastEMA, slowEMA *indicatorv2.EWMAStream
cross *indicatorv2.CrossStream
}

type Strategy struct {
Environment *bbgo.Environment
StandardIndicatorSet *bbgo.StandardIndicatorSet
Expand Down Expand Up @@ -121,6 +130,9 @@ type Strategy struct {
// BuyBelowNeutralSMA if true, the market maker will only place buy order when the current price is below the neutral band SMA.
BuyBelowNeutralSMA bool `json:"buyBelowNeutralSMA"`

// EMACrossSetting is used for defining ema cross signal to turn on/off buy
EMACrossSetting *EMACrossSetting `json:"emaCross"`

// NeutralBollinger is the smaller range of the bollinger band
// If price is in this band, it usually means the price is oscillating.
// If price goes out of this band, we tend to not place sell orders or buy orders
Expand Down Expand Up @@ -150,25 +162,24 @@ type Strategy struct {
ShadowProtection bool `json:"shadowProtection"`
ShadowProtectionRatio fixedpoint.Value `json:"shadowProtectionRatio"`

session *bbgo.ExchangeSession
book *types.StreamOrderBook

ExitMethods bbgo.ExitMethodSet `json:"exits"`

// persistence fields
Position *types.Position `json:"position,omitempty" persistence:"position"`
ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"`

session *bbgo.ExchangeSession
book *types.StreamOrderBook
orderExecutor *bbgo.GeneralOrderExecutor

groupID uint32

// defaultBoll is the BOLLINGER indicator we used for predicting the price.
defaultBoll *indicatorv2.BOLLStream

// neutralBoll is the neutral price section
neutralBoll *indicatorv2.BOLLStream

shouldBuy bool

// StrategyController
bbgo.StrategyController
}
Expand Down Expand Up @@ -202,6 +213,10 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.TrendEMA.Interval})
}

if s.EMACrossSetting != nil && s.EMACrossSetting.Enabled {
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.EMACrossSetting.Interval})
}

s.ExitMethods.SetAndSubscribe(session, s)
}

Expand Down Expand Up @@ -274,16 +289,15 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
Quantity: sellQuantity,
Price: askPrice,
Market: s.Market,
GroupID: s.groupID,
}

buyOrder := types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeLimitMaker,
Quantity: buyQuantity,
Price: bidPrice,
Market: s.Market,
GroupID: s.groupID,
}

var submitOrders []types.SubmitOrder
Expand Down Expand Up @@ -435,14 +449,20 @@ func (s *Strategy) placeOrders(ctx context.Context, midPrice fixedpoint.Value, k
}
}

if !s.shouldBuy {
log.Infof("shouldBuy is turned off, skip placing buy order")
canBuy = false
}

if canSell {
submitOrders = append(submitOrders, sellOrder)
}

if canBuy {
submitOrders = append(submitOrders, buyOrder)
}

// condition for lower the average cost
// condition for lowering the average cost
/*
if midPrice < s.Position.AverageCost.MulFloat64(1.0-s.MinProfitSpread.Float64()) && canBuy {
submitOrders = append(submitOrders, buyOrder)
Expand Down Expand Up @@ -478,9 +498,26 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
// StrategyController
s.Status = types.StrategyStatusRunning

s.shouldBuy = true
s.neutralBoll = session.Indicators(s.Symbol).BOLL(s.NeutralBollinger.IntervalWindow, s.NeutralBollinger.BandWidth)
s.defaultBoll = session.Indicators(s.Symbol).BOLL(s.DefaultBollinger.IntervalWindow, s.DefaultBollinger.BandWidth)

if s.EMACrossSetting != nil && s.EMACrossSetting.Enabled {
s.EMACrossSetting.fastEMA = session.Indicators(s.Symbol).EWMA(types.IntervalWindow{Interval: s.Interval, Window: s.EMACrossSetting.FastWindow})
s.EMACrossSetting.slowEMA = session.Indicators(s.Symbol).EWMA(types.IntervalWindow{Interval: s.Interval, Window: s.EMACrossSetting.SlowWindow})
s.EMACrossSetting.cross = indicatorv2.Cross(s.EMACrossSetting.fastEMA, s.EMACrossSetting.slowEMA)
s.EMACrossSetting.cross.OnUpdate(func(v float64) {
switch indicatorv2.CrossType(v) {
case indicatorv2.CrossOver:
s.shouldBuy = true
case indicatorv2.CrossUnder:
s.shouldBuy = false
// TODO: can partially close position when necessary
// s.orderExecutor.ClosePosition(ctx)
}
})
}

// Setup dynamic spread
if s.DynamicSpread.IsEnabled() {
if s.DynamicSpread.Interval == "" {
Expand Down Expand Up @@ -511,7 +548,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se

// calculate group id for orders
instanceID := s.InstanceID()
s.groupID = util.FNV32(instanceID)

// If position is nil, we need to allocate a new position for calculation
if s.Position == nil {
Expand Down Expand Up @@ -563,7 +599,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
})

session.UserDataStream.OnStart(func() {
if s.UseTickerPrice {
if !bbgo.IsBackTesting && s.UseTickerPrice {
ticker, err := s.session.Exchange.QueryTicker(ctx, s.Symbol)
if err != nil {
return
Expand Down

0 comments on commit c250fec

Please sign in to comment.