strategy:irr add klines box mean reversion

This commit is contained in:
austin362667 2022-10-19 16:02:00 +08:00
parent 303e2c8413
commit 612261c48c
3 changed files with 96 additions and 72 deletions

View File

@ -10,19 +10,26 @@ sessions:
binance: binance:
exchange: binance exchange: binance
envVarPrefix: binance envVarPrefix: binance
max:
exchange: max
envVarPrefix: max
ftx:
exchange: ftx
envVarPrefix: ftx
exchangeStrategies: exchangeStrategies:
- on: binance - on: binance
irr: irr:
symbol: BTCBUSD symbol: BTCBUSD
# in milliseconds(ms) # in milliseconds(ms)
hftInterval: 5 hftInterval: 1000
# indicator window # indicator window
window: 100 window: 0
# limit maker order quantity # maxima position in USD
quantity: 0.01 amount: 100
# bonus spread in USD quantity: 0.001
spread: 0.25 # minProfit pips in USD
pips: 0.0
# alpha1: negative return reversion # alpha1: negative return reversion
NR: true NR: true
# alpha2: moving average reversion # alpha2: moving average reversion

View File

@ -22,7 +22,6 @@ func (s *Strategy) InitDrawCommands(profit, cumProfit, cumProfitDollar types.Ser
return return
} }
bbgo.SendPhoto(&buffer) bbgo.SendPhoto(&buffer)
return
}) })
bbgo.RegisterCommand("/nav", "Draw Net Assets Value", func(reply interact.Reply) { 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 return
} }
bbgo.SendPhoto(&buffer) bbgo.SendPhoto(&buffer)
return
}) })
bbgo.RegisterCommand("/pnl", "Draw Cumulative Profit & Loss", func(reply interact.Reply) { 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 return
} }
bbgo.SendPhoto(&buffer) bbgo.SendPhoto(&buffer)
return
}) })
} }

View File

@ -21,7 +21,6 @@ const ID = "irr"
var one = fixedpoint.One var one = fixedpoint.One
var zero = fixedpoint.Zero var zero = fixedpoint.Zero
var Fee = 0.000 // taker fee % * 2, for upper bound
var log = logrus.WithField("strategy", ID) var log = logrus.WithField("strategy", ID)
@ -49,7 +48,7 @@ type Strategy struct {
orderExecutor *bbgo.GeneralOrderExecutor orderExecutor *bbgo.GeneralOrderExecutor
bbgo.QuantityOrAmount bbgo.QuantityOrAmount
Spread float64 `json:"spread"` MinProfit float64 `json:"pips"`
Interval int `json:"hftInterval"` Interval int `json:"hftInterval"`
NR bool `json:"NR"` NR bool `json:"NR"`
@ -61,8 +60,12 @@ type Strategy struct {
// realtime book ticker to submit order // realtime book ticker to submit order
obBuyPrice *atomic.Float64 obBuyPrice *atomic.Float64
obSellPrice *atomic.Float64 obSellPrice *atomic.Float64
// for posting LO
canBuy bool
canSell bool
// for getting close price // for getting close price
currentTradePrice *atomic.Float64 currentTradePrice *atomic.Float64
tradePriceSeries *types.Queue
// for negative return rate // for negative return rate
openPrice float64 openPrice float64
closePrice float64 closePrice float64
@ -343,6 +346,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.highestPrice = 0 s.highestPrice = 0
s.lowestPrice = s.sellPrice 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) 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.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 { if !bbgo.IsBackTesting {
@ -389,43 +413,67 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}) })
go func() { 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) intervalCloseTicker := time.NewTicker(time.Duration(s.Interval) * time.Millisecond)
defer intervalCloseTicker.Stop() defer intervalCloseTicker.Stop()
for { for {
select { select {
case <-intervalCloseTicker.C: case <-intervalCloseTicker.C:
s.orderExecutor.CancelNoWait(context.Background())
if s.currentTradePrice.Load() > 0 { if s.currentTradePrice.Load() > 0 {
s.orderExecutor.CancelNoWait(context.Background())
s.closePrice = s.currentTradePrice.Load() s.closePrice = s.currentTradePrice.Load()
//log.Infof("Close Price: %f", s.closePrice) log.Infof("Close Price: %f", s.closePrice)
// calculate real-time Negative Return if s.closePrice > 0 && s.openPrice > 0 {
s.rtNr.Update((s.openPrice - s.closePrice) / s.openPrice) direction := s.closePrice - s.openPrice
// calculate real-time Negative Return Rank klinDirections.Update(direction)
rtNrRank := 0. regimeShift := klinDirections.Index(0)*klinDirections.Index(1) < 0
if s.rtNr.Length() > 2 { if regimeShift && !started {
rtNrRank = s.rtNr.Rank(s.rtNr.Length()).Last() / float64(s.rtNr.Length()) 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: case <-s.stopC:
log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) 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() { 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) intervalOpenTicker := time.NewTicker(time.Duration(s.Interval) * time.Millisecond)
defer intervalOpenTicker.Stop() defer intervalOpenTicker.Stop()
for { for {
select { select {
case <-intervalOpenTicker.C: case <-intervalOpenTicker.C:
time.Sleep(50 * time.Microsecond) time.Sleep(time.Duration(s.Interval/10) * time.Millisecond)
if s.currentTradePrice.Load() > 0 { if s.currentTradePrice.Load() > 0 && s.closePrice > 0 {
s.openPrice = s.currentTradePrice.Load() s.openPrice = s.currentTradePrice.Load()
//log.Infof("Open Price: %f", s.openPrice) log.Infof("Open Price: %f", s.openPrice)
} }
case <-s.stopC: case <-s.stopC:
log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) 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() balances := s.session.GetAccount().Balances()
return balances[s.Market.BaseCurrency].Total().Mul(price).Add(balances[s.Market.QuoteCurrency].Total()) 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)
}
}
}