mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
strategy:irr add klines box mean reversion
This commit is contained in:
parent
303e2c8413
commit
612261c48c
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user