From 612261c48c0c8df7306d19ab56b4a3a28aac68c0 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Wed, 19 Oct 2022 16:02:00 +0800 Subject: [PATCH] strategy:irr add klines box mean reversion --- config/irr.yaml | 19 +++-- pkg/strategy/irr/draw.go | 4 - pkg/strategy/irr/strategy.go | 145 ++++++++++++++++++++--------------- 3 files changed, 96 insertions(+), 72 deletions(-) diff --git a/config/irr.yaml b/config/irr.yaml index 8fa9ac329..eb899c093 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -10,19 +10,26 @@ sessions: binance: exchange: binance envVarPrefix: binance + max: + exchange: max + envVarPrefix: max + ftx: + exchange: ftx + envVarPrefix: ftx exchangeStrategies: - on: binance irr: symbol: BTCBUSD # in milliseconds(ms) - hftInterval: 5 + hftInterval: 1000 # indicator window - window: 100 - # limit maker order quantity - quantity: 0.01 - # bonus spread in USD - spread: 0.25 + window: 0 + # maxima position in USD + amount: 100 + quantity: 0.001 + # minProfit pips in USD + pips: 0.0 # alpha1: negative return reversion NR: true # alpha2: moving average reversion diff --git a/pkg/strategy/irr/draw.go b/pkg/strategy/irr/draw.go index b038ec702..fdf4ea9e2 100644 --- a/pkg/strategy/irr/draw.go +++ b/pkg/strategy/irr/draw.go @@ -22,7 +22,6 @@ func (s *Strategy) InitDrawCommands(profit, cumProfit, cumProfitDollar types.Ser return } bbgo.SendPhoto(&buffer) - return }) bbgo.RegisterCommand("/nav", "Draw Net Assets Value", func(reply interact.Reply) { @@ -34,8 +33,6 @@ func (s *Strategy) InitDrawCommands(profit, cumProfit, cumProfitDollar types.Ser return } bbgo.SendPhoto(&buffer) - return - }) bbgo.RegisterCommand("/pnl", "Draw Cumulative Profit & Loss", func(reply interact.Reply) { @@ -47,7 +44,6 @@ func (s *Strategy) InitDrawCommands(profit, cumProfit, cumProfitDollar types.Ser return } bbgo.SendPhoto(&buffer) - return }) } diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 68ac52961..c75c9e29a 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -21,7 +21,6 @@ const ID = "irr" var one = fixedpoint.One var zero = fixedpoint.Zero -var Fee = 0.000 // taker fee % * 2, for upper bound var log = logrus.WithField("strategy", ID) @@ -49,7 +48,7 @@ type Strategy struct { orderExecutor *bbgo.GeneralOrderExecutor bbgo.QuantityOrAmount - Spread float64 `json:"spread"` + MinProfit float64 `json:"pips"` Interval int `json:"hftInterval"` NR bool `json:"NR"` @@ -61,8 +60,12 @@ type Strategy struct { // realtime book ticker to submit order obBuyPrice *atomic.Float64 obSellPrice *atomic.Float64 + // for posting LO + canBuy bool + canSell bool // for getting close price currentTradePrice *atomic.Float64 + tradePriceSeries *types.Queue // for negative return rate openPrice float64 closePrice float64 @@ -343,6 +346,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.highestPrice = 0 s.lowestPrice = s.sellPrice } + + if trade.Side == types.SideTypeBuy { + s.canSell = true + s.canBuy = false + } else if trade.Side == types.SideTypeSell { + s.canBuy = true + s.canSell = false + } }) s.InitDrawCommands(&profitSlice, &cumProfitSlice, &cumProfitDollarSlice) @@ -374,7 +385,20 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.rtWeight = types.NewQueue(s.Window) - s.currentTradePrice = atomic.NewFloat64(0) + s.currentTradePrice = atomic.NewFloat64(0.) + s.tradePriceSeries = types.NewQueue(2) + + // adverse selection based on order flow dynamics + s.canBuy = true + s.canSell = true + + s.closePrice = 0. // atomic.NewFloat64(0) + s.openPrice = 0. //atomic.NewFloat64(0) + klinDirections := types.NewQueue(100) + started := false + boxOpenPrice := 0. + boxClosePrice := 0. + boxCounter := 0 if !bbgo.IsBackTesting { @@ -389,43 +413,67 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se }) go func() { + time.Sleep(time.Duration(s.Interval-int(time.Now().UnixMilli())%s.Interval) * time.Millisecond) + intervalCloseTicker := time.NewTicker(time.Duration(s.Interval) * time.Millisecond) defer intervalCloseTicker.Stop() for { select { case <-intervalCloseTicker.C: + + s.orderExecutor.CancelNoWait(context.Background()) + if s.currentTradePrice.Load() > 0 { - s.orderExecutor.CancelNoWait(context.Background()) s.closePrice = s.currentTradePrice.Load() - //log.Infof("Close Price: %f", s.closePrice) - // calculate real-time Negative Return - s.rtNr.Update((s.openPrice - s.closePrice) / s.openPrice) - // calculate real-time Negative Return Rank - rtNrRank := 0. - if s.rtNr.Length() > 2 { - rtNrRank = s.rtNr.Rank(s.rtNr.Length()).Last() / float64(s.rtNr.Length()) + log.Infof("Close Price: %f", s.closePrice) + if s.closePrice > 0 && s.openPrice > 0 { + direction := s.closePrice - s.openPrice + klinDirections.Update(direction) + regimeShift := klinDirections.Index(0)*klinDirections.Index(1) < 0 + if regimeShift && !started { + boxOpenPrice = s.openPrice + started = true + boxCounter = 0 + log.Infof("box started at price: %f", boxOpenPrice) + } else if regimeShift && started { + boxClosePrice = s.openPrice + started = false + log.Infof("box ended at price: %f with time length: %d", boxClosePrice, boxCounter) + // box ending, should re-balance position + nirr := fixedpoint.NewFromFloat(((boxOpenPrice - boxClosePrice) / boxOpenPrice) / (float64(boxCounter) + 1)) + qty := s.QuantityOrAmount.CalculateQuantity(fixedpoint.Value(boxClosePrice)) + qty = qty.Mul(nirr.Abs().Div(fixedpoint.NewFromInt(1000))) + log.Infof("Alpha: %f with Diff Qty: %f", nirr.Float64(), qty.Float64()) + if nirr.Float64() < 0 { + _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeSell, + Quantity: s.Quantity, + Type: types.OrderTypeLimitMaker, + Price: fixedpoint.NewFromFloat(s.obSellPrice.Load()), + Tag: "irr re-balance: sell", + }) + if err != nil { + log.WithError(err) + } + } else if nirr.Float64() > 0 { + _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeBuy, + Quantity: s.Quantity, + Type: types.OrderTypeLimitMaker, + Price: fixedpoint.NewFromFloat(s.obBuyPrice.Load()), + Tag: "irr re-balance: buy", + }) + if err != nil { + log.WithError(err) + } + } + } else { + boxCounter++ + } } - // calculate real-time Mean Reversion - s.rtMaFast.Update(s.closePrice) - s.rtMaSlow.Update(s.closePrice) - s.rtMr.Update((s.rtMaSlow.Mean() - s.rtMaFast.Mean()) / s.rtMaSlow.Mean()) - // calculate real-time Mean Reversion Rank - rtMrRank := 0. - if s.rtMr.Length() > 2 { - rtMrRank = s.rtMr.Rank(s.rtMr.Length()).Last() / float64(s.rtMr.Length()) - } - alpha := 0. - if s.NR && s.MR { - alpha = (rtNrRank + rtMrRank) / 2 - } else if s.NR && !s.MR { - alpha = rtNrRank - } else if !s.NR && s.MR { - alpha = rtMrRank - } - s.rtWeight.Update(alpha) - log.Infof("Alpha: %f/1.0", s.rtWeight.Last()) - s.rebalancePosition(s.obBuyPrice.Load(), s.obSellPrice.Load(), s.rtWeight.Last()) } case <-s.stopC: log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) @@ -439,15 +487,16 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se }() go func() { + time.Sleep(time.Duration(s.Interval-int(time.Now().UnixMilli())%s.Interval) * time.Millisecond) intervalOpenTicker := time.NewTicker(time.Duration(s.Interval) * time.Millisecond) defer intervalOpenTicker.Stop() for { select { case <-intervalOpenTicker.C: - time.Sleep(50 * time.Microsecond) - if s.currentTradePrice.Load() > 0 { + time.Sleep(time.Duration(s.Interval/10) * time.Millisecond) + if s.currentTradePrice.Load() > 0 && s.closePrice > 0 { s.openPrice = s.currentTradePrice.Load() - //log.Infof("Open Price: %f", s.openPrice) + log.Infof("Open Price: %f", s.openPrice) } case <-s.stopC: log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) @@ -486,31 +535,3 @@ func (s *Strategy) CalcAssetValue(price fixedpoint.Value) fixedpoint.Value { balances := s.session.GetAccount().Balances() return balances[s.Market.BaseCurrency].Total().Mul(price).Add(balances[s.Market.QuoteCurrency].Total()) } - -func (s *Strategy) rebalancePosition(bestBid, bestAsk float64, w float64) { - if w < 0.5 { - _, errB := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ - Symbol: s.Symbol, - Side: types.SideTypeBuy, - Quantity: s.Quantity, - Type: types.OrderTypeLimitMaker, - Price: fixedpoint.NewFromFloat(bestBid - s.Spread), - Tag: "irr short: buy", - }) - if errB != nil { - log.WithError(errB) - } - } else if w > 0.5 { - _, errA := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ - Symbol: s.Symbol, - Side: types.SideTypeSell, - Quantity: s.Quantity, - Type: types.OrderTypeLimitMaker, - Price: fixedpoint.NewFromFloat(bestAsk + s.Spread), - Tag: "irr long: buy", - }) - if errA != nil { - log.WithError(errA) - } - } -}