From a3dd93dd9ada931fd96acd960748123de67c2a75 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Tue, 11 Oct 2022 21:49:41 +0800 Subject: [PATCH 01/11] strategy:irr: add backtest/realtime ability --- config/irr.yaml | 10 +- pkg/strategy/irr/strategy.go | 179 +++++++++++++++++++++++++---------- 2 files changed, 134 insertions(+), 55 deletions(-) diff --git a/config/irr.yaml b/config/irr.yaml index 5aa782bc5..95954f4d5 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -15,9 +15,10 @@ exchangeStrategies: - on: binance irr: symbol: BTCBUSD - interval: 1m + interval: 1s window: 120 - amount: 5_000.0 + amount: 500.0 + humpThreshold: 0.000025 # Draw pnl drawGraph: true graphPNLPath: "./pnl.png" @@ -26,8 +27,9 @@ exchangeStrategies: backtest: sessions: - binance - startTime: "2022-09-01" - endTime: "2022-10-04" + startTime: "2022-10-09" + endTime: "2022-10-11" +# syncSecKLines: true symbols: - BTCBUSD accounts: diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 75e5910d6..27fc9c564 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -9,17 +9,19 @@ import ( "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/indicator" "github.com/c9s/bbgo/pkg/types" + "github.com/c9s/bbgo/pkg/util" + "github.com/sirupsen/logrus" + "math" "os" "sync" - - "github.com/sirupsen/logrus" + "time" ) const ID = "irr" var one = fixedpoint.One var zero = fixedpoint.Zero -var Fee = 0.0008 // taker fee % * 2, for upper bound +var Fee = 0.000 // taker fee % * 2, for upper bound var log = logrus.WithField("strategy", ID) @@ -47,7 +49,16 @@ type Strategy struct { orderExecutor *bbgo.GeneralOrderExecutor bbgo.QuantityOrAmount - nrr *NRR + + HumpThreshold float64 `json:"humpThreshold"` + + lastTwoPrices *types.Queue + // for back-test + Nrr *NRR + // for realtime book ticker + lastPrice fixedpoint.Value + rtNrr *types.Queue + stopC chan struct{} // StrategyController bbgo.StrategyController @@ -194,10 +205,10 @@ func (r *AccumulatedProfitReport) Output(symbol string) { } func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { - session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) - if !bbgo.IsBackTesting { - session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{}) + session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{}) + } else { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) } s.ExitMethods.SetAndSubscribe(session, s) @@ -273,9 +284,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.AccumulatedProfitReport.RecordProfit(profit.Profit) }) - // s.orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, profit fixedpoint.Value, netProfit fixedpoint.Value) { - // s.AccumulatedProfitReport.RecordTrade(trade.Fee) - // }) session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval1d, func(kline types.KLine) { s.AccumulatedProfitReport.DailyUpdate(s.TradeStats) })) @@ -319,63 +327,59 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } }) + s.InitDrawCommands(&profitSlice, &cumProfitSlice) + s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) { bbgo.Sync(ctx, s) }) s.orderExecutor.Bind() s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol) - for _, method := range s.ExitMethods { - method.Bind(session, s.orderExecutor) - } - + //back-test only, because 1s delayed a lot kLineStore, _ := s.session.MarketDataStore(s.Symbol) - s.nrr = &NRR{IntervalWindow: types.IntervalWindow{Window: 2, Interval: s.Interval}, RankingWindow: s.Window} - s.nrr.BindK(s.session.MarketDataStream, s.Symbol, s.Interval) - if klines, ok := kLineStore.KLinesOfInterval(s.nrr.Interval); ok { - s.nrr.LoadK((*klines)[0:]) + s.Nrr = &NRR{IntervalWindow: types.IntervalWindow{Window: 2, Interval: s.Interval}, RankingWindow: s.Window} + s.Nrr.BindK(s.session.MarketDataStream, s.Symbol, s.Interval) + if klines, ok := kLineStore.KLinesOfInterval(s.Nrr.Interval); ok { + s.Nrr.LoadK((*klines)[0:]) } - // startTime := s.Environment.StartTime() - // s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1h, startTime)) + s.lastTwoPrices = types.NewQueue(2) // current price & previous price + s.rtNrr = types.NewQueue(s.Window) + if !bbgo.IsBackTesting { - s.session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { + s.stopC = make(chan struct{}) - // ts_rank(): transformed to [0~1] which divided equally - // queued first signal as its initial process - // important: delayed signal in order to submit order at current kline close (a.k.a. next open while in production) - // instead of right in current kline open + go func() { + secondTicker := time.NewTicker(util.MillisecondsJitter(s.Interval.Duration(), 200)) + defer secondTicker.Stop() - // alpha-weighted assets (inventory and capital) - targetBase := s.QuantityOrAmount.CalculateQuantity(kline.Close).Mul(fixedpoint.NewFromFloat(s.nrr.RankedValues.Index(1))) - diffQty := targetBase.Sub(s.Position.Base) + for { + select { + case <-secondTicker.C: + s.rebalancePosition(true) + case <-s.stopC: + log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) + return - log.Infof("decision alpah: %f, ranked negative return: %f, current position: %f, target position diff: %f", s.nrr.RankedValues.Index(1), s.nrr.RankedValues.Last(), s.Position.Base.Float64(), diffQty.Float64()) + case <-ctx.Done(): + log.Warnf("%s goroutine stopped, due to the cancelled context", s.Symbol) + return + } + } + }() - // use kline direction to prevent reversing position too soon - if diffQty.Sign() > 0 { // && kline.Direction() >= 0 - _, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ - Symbol: s.Symbol, - Side: types.SideTypeBuy, - Quantity: diffQty.Abs(), - Type: types.OrderTypeMarket, - Tag: "irr buy more", - }) - } else if diffQty.Sign() < 0 { // && kline.Direction() <= 0 - _, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ - Symbol: s.Symbol, - Side: types.SideTypeSell, - Quantity: diffQty.Abs(), - Type: types.OrderTypeMarket, - Tag: "irr sell more", - }) - } - - })) + s.session.MarketDataStream.OnBookTickerUpdate(func(bt types.BookTicker) { + s.lastPrice = bt.Buy.Add(bt.Sell).Div(fixedpoint.Two) + }) + } else { + s.session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { + s.lastPrice = kline.Close + s.rebalancePosition(false) + }) + } bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() - // Output accumulated profit report if bbgo.IsBackTesting { defer s.AccumulatedProfitReport.Output(s.Symbol) @@ -385,8 +389,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se log.WithError(err).Errorf("cannot draw graph") } } + } else { + close(s.stopC) } - _, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String()) _ = s.orderExecutor.GracefulCancel(ctx) }) @@ -398,3 +403,75 @@ 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(rt bool) { + + s.lastTwoPrices.Update(s.lastPrice.Float64()) + if s.lastTwoPrices.Length() >= 2 { + log.Infof("Interval Closed Price: %f", s.lastTwoPrices.Last()) + // main idea: negative return + nr := -1 * (s.lastTwoPrices.Last()/s.lastTwoPrices.Index(1) - 1) + if rt { + // hump operation to reduce noise + // update nirr indicator when above threshold + if math.Abs(s.rtNrr.Last()-nr) < s.HumpThreshold { + s.rtNrr.Update(s.rtNrr.Last()) + } else { + s.rtNrr.Update(nr) + return + } + } else { + if math.Abs(s.Nrr.Last()-s.Nrr.Index(1)) < s.HumpThreshold { + return + } + } + + // when have enough Nrr to do ts_rank() + if (s.rtNrr.Length() >= s.Window && rt) || (s.Nrr.Length() >= s.Window && !rt) { + + // alpha-weighted assets (inventory and capital) + position := s.orderExecutor.Position() + // weight: 0~1, since it's a long only strategy + weight := fixedpoint.NewFromFloat(s.rtNrr.Rank(s.Window).Last() / float64(s.Window)) + if !rt { + weight = fixedpoint.NewFromFloat(s.Nrr.Rank(s.Window).Last() / float64(s.Window)) + } + targetBase := s.QuantityOrAmount.CalculateQuantity(fixedpoint.NewFromFloat(s.lastTwoPrices.Mean(2))).Mul(weight) + + // to buy/sell quantity + diffQty := targetBase.Sub(position.Base) + log.Infof("Alpha: %f/1.0, Target Position Diff: %f", weight.Float64(), diffQty.Float64()) + + // ignore small changes + if diffQty.Abs().Float64() < 0.001 { + return + } + // re-balance position + if diffQty.Sign() > 0 { + _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeBuy, + Quantity: diffQty.Abs(), + Type: types.OrderTypeMarket, + //Price: bt.Sell, + Tag: "irr re-balance: buy", + }) + if err != nil { + log.WithError(err) + } + } else if diffQty.Sign() < 0 { + _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeSell, + Quantity: diffQty.Abs(), + Type: types.OrderTypeMarket, + //Price: bt.Buy, + Tag: "irr re-balance: sell", + }) + if err != nil { + log.WithError(err) + } + } + } + } +} From 150c37995e43cc2080fb0742abd5b070df723f99 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Mon, 17 Oct 2022 21:34:55 +0800 Subject: [PATCH 02/11] strategy:irr redesign trigger --- config/irr.yaml | 20 +-- pkg/strategy/irr/draw.go | 35 ++-- pkg/strategy/irr/neg_return_rate.go | 24 +-- pkg/strategy/irr/strategy.go | 248 +++++++++++++++------------- 4 files changed, 169 insertions(+), 158 deletions(-) diff --git a/config/irr.yaml b/config/irr.yaml index 95954f4d5..9e8fd56ac 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -15,25 +15,11 @@ exchangeStrategies: - on: binance irr: symbol: BTCBUSD - interval: 1s - window: 120 + # in milliseconds(ms) + hftInterval: 1000 + # maxima position in USD amount: 500.0 - humpThreshold: 0.000025 # Draw pnl drawGraph: true graphPNLPath: "./pnl.png" graphCumPNLPath: "./cumpnl.png" - -backtest: - sessions: - - binance - startTime: "2022-10-09" - endTime: "2022-10-11" -# syncSecKLines: true - symbols: - - BTCBUSD - accounts: - binance: - takerFeeRate: 0.0 - balances: - BUSD: 5_000.0 diff --git a/pkg/strategy/irr/draw.go b/pkg/strategy/irr/draw.go index cf9f98bfa..98f99d818 100644 --- a/pkg/strategy/irr/draw.go +++ b/pkg/strategy/irr/draw.go @@ -11,26 +11,41 @@ import ( "github.com/wcharczuk/go-chart/v2" ) -func (s *Strategy) InitDrawCommands(profit, cumProfit types.Series) { - bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) { +func (s *Strategy) InitDrawCommands(profit, cumProfit, cumProfitDollar types.Series) { + bbgo.RegisterCommand("/rt", "Draw Return Rate(%) Per Trade", func(reply interact.Reply) { + canvas := DrawPNL(s.InstanceID(), profit) var buffer bytes.Buffer if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render pnl in drift") - reply.Message(fmt.Sprintf("[error] cannot render pnl in ewo: %v", err)) + log.WithError(err).Errorf("cannot render return in irr") + reply.Message(fmt.Sprintf("[error] cannot render return in irr: %v", err)) return } - bbgo.SendPhoto(&buffer) + go bbgo.SendPhoto(&buffer) }) - bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) { + bbgo.RegisterCommand("/nav", "Draw Net Assets Value", func(reply interact.Reply) { + canvas := DrawCumPNL(s.InstanceID(), cumProfit) var buffer bytes.Buffer if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render cumpnl in drift") - reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err)) + log.WithError(err).Errorf("cannot render nav in irr") + reply.Message(fmt.Sprintf("[error] canot render nav in irr: %v", err)) return } - bbgo.SendPhoto(&buffer) + go bbgo.SendPhoto(&buffer) + + }) + bbgo.RegisterCommand("/pnl", "Draw Cumulative Profit & Loss", func(reply interact.Reply) { + + canvas := DrawCumPNL(s.InstanceID(), cumProfitDollar) + var buffer bytes.Buffer + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render pnl in irr") + reply.Message(fmt.Sprintf("[error] canot render pnl in irr: %v", err)) + return + } + go bbgo.SendPhoto(&buffer) + }) } @@ -77,7 +92,7 @@ func DrawPNL(instanceID string, profit types.Series) *types.Canvas { func DrawCumPNL(instanceID string, cumProfit types.Series) *types.Canvas { canvas := types.NewCanvas(instanceID) - canvas.PlotRaw("cummulative pnl", cumProfit, cumProfit.Length()) + canvas.PlotRaw("cumulative pnl", cumProfit, cumProfit.Length()) canvas.YAxis = chart.YAxis{ ValueFormatter: func(v interface{}) string { if vf, isFloat := v.(float64); isFloat { diff --git a/pkg/strategy/irr/neg_return_rate.go b/pkg/strategy/irr/neg_return_rate.go index 09cee398b..b3ec5edcc 100644 --- a/pkg/strategy/irr/neg_return_rate.go +++ b/pkg/strategy/irr/neg_return_rate.go @@ -30,18 +30,21 @@ type NRR struct { var _ types.SeriesExtend = &NRR{} -func (inc *NRR) Update(price float64) { +func (inc *NRR) Update(openPrice, closePrice float64) { if inc.SeriesBase.Series == nil { inc.SeriesBase.Series = inc inc.Prices = types.NewQueue(inc.Window) } - inc.Prices.Update(price) + inc.Prices.Update(closePrice) if inc.Prices.Length() < inc.Window { return } - irr := (inc.Prices.Last() / inc.Prices.Index(inc.Window-1)) - 1 + // D0 + irr := openPrice - closePrice + // D1 + // -1*((inc.Prices.Last() / inc.Prices.Index(inc.Window-1)) - 1) - inc.Values.Push(-irr) // neg ret here + inc.Values.Push(irr) // neg ret here inc.RankedValues.Push(inc.Rank(inc.RankingWindow).Last() / float64(inc.RankingWindow)) // ranked neg ret here } @@ -75,7 +78,7 @@ func (inc *NRR) PushK(k types.KLine) { return } - inc.Update(indicator.KLineClosePriceMapper(k)) + inc.Update(indicator.KLineOpenPriceMapper(k), indicator.KLineClosePriceMapper(k)) inc.EndTime = k.EndTime.Time() inc.EmitUpdate(inc.Last()) } @@ -86,14 +89,3 @@ func (inc *NRR) LoadK(allKLines []types.KLine) { } inc.EmitUpdate(inc.Last()) } - -//func calculateReturn(klines []types.KLine, window int, val KLineValueMapper) (float64, error) { -// length := len(klines) -// if length == 0 || length < window { -// return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window) -// } -// -// rate := val(klines[length-1])/val(klines[length-2]) - 1 -// -// return rate, nil -//} diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 27fc9c564..f7b14c174 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -9,12 +9,10 @@ import ( "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/indicator" "github.com/c9s/bbgo/pkg/types" - "github.com/c9s/bbgo/pkg/util" "github.com/sirupsen/logrus" - "math" + "go.uber.org/atomic" "os" "sync" - "time" ) const ID = "irr" @@ -50,15 +48,26 @@ type Strategy struct { bbgo.QuantityOrAmount - HumpThreshold float64 `json:"humpThreshold"` + Interval int `json:"hftInterval"` - lastTwoPrices *types.Queue // for back-test Nrr *NRR - // for realtime book ticker - lastPrice fixedpoint.Value - rtNrr *types.Queue - stopC chan struct{} + Ma *indicator.SMA + // realtime book ticker to submit order + obBuyPrice *atomic.Float64 + obSellPrice *atomic.Float64 + // for negative return rate + openPrice float64 + closePrice float64 + rtNr *types.Queue + // for moving average reversion + rtMaFast *types.Queue + rtMaSlow *types.Queue + rtMr *types.Queue + + // for final alpha (Nr+Mr)/2 + rtWeight *types.Queue + stopC chan struct{} // StrategyController bbgo.StrategyController @@ -206,12 +215,10 @@ func (r *AccumulatedProfitReport) Output(symbol string) { func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { if !bbgo.IsBackTesting { + session.Subscribe(types.AggTradeChannel, s.Symbol, types.SubscribeOptions{}) session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{}) - } else { - session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) } - - s.ExitMethods.SetAndSubscribe(session, s) + //session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) } func (s *Strategy) ID() string { @@ -249,7 +256,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // Cancel active orders _ = s.orderExecutor.GracefulCancel(ctx) // Close 100% position - // _ = s.ClosePosition(ctx, fixedpoint.One) + _ = s.orderExecutor.ClosePosition(ctx, fixedpoint.One) }) // initial required information @@ -294,6 +301,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se price, _ := session.LastPrice(s.Symbol) initAsset := s.CalcAssetValue(price).Float64() cumProfitSlice := floats.Slice{initAsset, initAsset} + profitDollarSlice := floats.Slice{0, 0} + cumProfitDollarSlice := floats.Slice{0, 0} s.orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, profit fixedpoint.Value, netProfit fixedpoint.Value) { if bbgo.IsBackTesting { @@ -309,6 +318,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se profitSlice.Update(s.sellPrice / price) cumProfitSlice.Update(s.CalcAssetValue(trade.Price).Float64()) } + profitDollarSlice.Update(profit.Float64()) + cumProfitDollarSlice.Update(profitDollarSlice.Sum()) if s.Position.IsDust(trade.Price) { s.buyPrice = 0 s.sellPrice = 0 @@ -327,7 +338,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } }) - s.InitDrawCommands(&profitSlice, &cumProfitSlice) + s.InitDrawCommands(&profitSlice, &cumProfitSlice, &cumProfitDollarSlice) s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) { bbgo.Sync(ctx, s) @@ -336,45 +347,81 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol) //back-test only, because 1s delayed a lot - kLineStore, _ := s.session.MarketDataStore(s.Symbol) - s.Nrr = &NRR{IntervalWindow: types.IntervalWindow{Window: 2, Interval: s.Interval}, RankingWindow: s.Window} - s.Nrr.BindK(s.session.MarketDataStream, s.Symbol, s.Interval) - if klines, ok := kLineStore.KLinesOfInterval(s.Nrr.Interval); ok { - s.Nrr.LoadK((*klines)[0:]) - } + //kLineStore, _ := s.session.MarketDataStore(s.Symbol) + //s.Nrr = &NRR{IntervalWindow: types.IntervalWindow{Window: 2, Interval: s.Interval}, RankingWindow: s.Window} + //s.Nrr.BindK(s.session.MarketDataStream, s.Symbol, s.Interval) + //if klines, ok := kLineStore.KLinesOfInterval(s.Nrr.Interval); ok { + // s.Nrr.LoadK((*klines)[0:]) + //} + //s.Ma = &indicator.SMA{IntervalWindow: types.IntervalWindow{Window: s.Window, Interval: s.Interval}} + //s.Ma.BindK(s.session.MarketDataStream, s.Symbol, s.Interval) + //if klines, ok := kLineStore.KLinesOfInterval(s.Ma.Interval); ok { + // s.Ma.LoadK((*klines)[0:]) + //} + + s.rtNr = types.NewQueue(100) + + s.rtMaFast = types.NewQueue(1) + s.rtMaSlow = types.NewQueue(5) + s.rtMr = types.NewQueue(100) + + s.rtWeight = types.NewQueue(100) + + currentRoundTime := int64(0) + previousRoundTime := int64(0) + + currentTradePrice := 0. + previousTradePrice := 0. - s.lastTwoPrices = types.NewQueue(2) // current price & previous price - s.rtNrr = types.NewQueue(s.Window) if !bbgo.IsBackTesting { - s.stopC = make(chan struct{}) - - go func() { - secondTicker := time.NewTicker(util.MillisecondsJitter(s.Interval.Duration(), 200)) - defer secondTicker.Stop() - - for { - select { - case <-secondTicker.C: - s.rebalancePosition(true) - case <-s.stopC: - log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) - return - - case <-ctx.Done(): - log.Warnf("%s goroutine stopped, due to the cancelled context", s.Symbol) - return - } - } - }() - s.session.MarketDataStream.OnBookTickerUpdate(func(bt types.BookTicker) { - s.lastPrice = bt.Buy.Add(bt.Sell).Div(fixedpoint.Two) + // quote order book price + s.obBuyPrice = atomic.NewFloat64(bt.Buy.Float64()) + s.obSellPrice = atomic.NewFloat64(bt.Sell.Float64()) }) - } else { - s.session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { - s.lastPrice = kline.Close - s.rebalancePosition(false) + + s.session.MarketDataStream.OnAggTrade(func(trade types.Trade) { + // rounding to 1000 milliseconds if hftInterval is set to 1000 + currentRoundTime = trade.Time.UnixMilli() % int64(s.Interval) + currentTradePrice = trade.Price.Float64() + if currentRoundTime < previousRoundTime { + + s.openPrice = s.closePrice + // D0 strategy can use now data + // D1 strategy only use previous data (we're here) + s.closePrice = previousTradePrice + //log.Infof("Previous Close Price: %f", s.closePrice) + //log.Infof("Previous Open Price: %f", s.openPrice) + log.Infof("Now Open Price: %f", currentTradePrice) + s.orderExecutor.CancelNoWait(ctx) + // calculate real-time Negative Return + s.rtNr.Update(s.openPrice - s.closePrice) + // calculate real-time Negative Return Rank + rtNrRank := 0. + if s.rtNr.Length() >= 100 { + rtNrRank = s.rtNr.Rank(s.rtNr.Length()).Last() / float64(s.rtNr.Length()) + } + // 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() >= 100 { + rtMrRank = s.rtMr.Rank(s.rtMr.Length()).Last() / float64(s.rtMr.Length()) + } + s.rtWeight.Update((rtNrRank + rtMrRank) / 2) + rtWeightRank := 0. + if s.rtWeight.Length() >= 100 { + rtWeightRank = s.rtWeight.Rank(s.rtWeight.Length()).Last() / float64(s.rtWeight.Length()) + } + log.Infof("Alpha: %f/1.0", rtWeightRank) + s.rebalancePosition(s.obBuyPrice.Load(), s.obSellPrice.Load(), rtWeightRank) + } + + previousRoundTime = currentRoundTime + previousTradePrice = currentTradePrice }) } @@ -404,74 +451,45 @@ func (s *Strategy) CalcAssetValue(price fixedpoint.Value) fixedpoint.Value { return balances[s.Market.BaseCurrency].Total().Mul(price).Add(balances[s.Market.QuoteCurrency].Total()) } -func (s *Strategy) rebalancePosition(rt bool) { +func (s *Strategy) rebalancePosition(bestBid, bestAsk float64, w float64) { + // alpha-weighted assets (inventory and capital) + position := s.orderExecutor.Position() + p := fixedpoint.NewFromFloat((bestBid + bestAsk) / 2) - s.lastTwoPrices.Update(s.lastPrice.Float64()) - if s.lastTwoPrices.Length() >= 2 { - log.Infof("Interval Closed Price: %f", s.lastTwoPrices.Last()) - // main idea: negative return - nr := -1 * (s.lastTwoPrices.Last()/s.lastTwoPrices.Index(1) - 1) - if rt { - // hump operation to reduce noise - // update nirr indicator when above threshold - if math.Abs(s.rtNrr.Last()-nr) < s.HumpThreshold { - s.rtNrr.Update(s.rtNrr.Last()) - } else { - s.rtNrr.Update(nr) - return - } - } else { - if math.Abs(s.Nrr.Last()-s.Nrr.Index(1)) < s.HumpThreshold { - return - } + targetBase := s.QuantityOrAmount.CalculateQuantity(p).Mul(fixedpoint.NewFromFloat(w)) + + // to buy/sell quantity + diffQty := targetBase.Sub(position.Base) + log.Infof("Target Position Diff: %f", diffQty.Float64()) + + // ignore small changes + if diffQty.Abs().Float64() < 0.0005 { + return + } + + if diffQty.Sign() > 0 { + _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeBuy, + Quantity: diffQty.Abs(), + Type: types.OrderTypeLimit, + Price: fixedpoint.NewFromFloat(bestBid), + Tag: "irr re-balance: buy", + }) + if err != nil { + log.WithError(err) } - - // when have enough Nrr to do ts_rank() - if (s.rtNrr.Length() >= s.Window && rt) || (s.Nrr.Length() >= s.Window && !rt) { - - // alpha-weighted assets (inventory and capital) - position := s.orderExecutor.Position() - // weight: 0~1, since it's a long only strategy - weight := fixedpoint.NewFromFloat(s.rtNrr.Rank(s.Window).Last() / float64(s.Window)) - if !rt { - weight = fixedpoint.NewFromFloat(s.Nrr.Rank(s.Window).Last() / float64(s.Window)) - } - targetBase := s.QuantityOrAmount.CalculateQuantity(fixedpoint.NewFromFloat(s.lastTwoPrices.Mean(2))).Mul(weight) - - // to buy/sell quantity - diffQty := targetBase.Sub(position.Base) - log.Infof("Alpha: %f/1.0, Target Position Diff: %f", weight.Float64(), diffQty.Float64()) - - // ignore small changes - if diffQty.Abs().Float64() < 0.001 { - return - } - // re-balance position - if diffQty.Sign() > 0 { - _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ - Symbol: s.Symbol, - Side: types.SideTypeBuy, - Quantity: diffQty.Abs(), - Type: types.OrderTypeMarket, - //Price: bt.Sell, - Tag: "irr re-balance: buy", - }) - if err != nil { - log.WithError(err) - } - } else if diffQty.Sign() < 0 { - _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ - Symbol: s.Symbol, - Side: types.SideTypeSell, - Quantity: diffQty.Abs(), - Type: types.OrderTypeMarket, - //Price: bt.Buy, - Tag: "irr re-balance: sell", - }) - if err != nil { - log.WithError(err) - } - } + } else if diffQty.Sign() < 0 { + _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeSell, + Quantity: diffQty.Abs(), + Type: types.OrderTypeLimit, + Price: fixedpoint.NewFromFloat(bestAsk), + Tag: "irr re-balance: sell", + }) + if err != nil { + log.WithError(err) } } } From 2b397940b80901529c1562ea5095e597b885dd4e Mon Sep 17 00:00:00 2001 From: austin362667 Date: Mon, 17 Oct 2022 22:13:22 +0800 Subject: [PATCH 03/11] strategy:irr fix draw goroutine --- pkg/strategy/irr/draw.go | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/pkg/strategy/irr/draw.go b/pkg/strategy/irr/draw.go index 98f99d818..b4346b815 100644 --- a/pkg/strategy/irr/draw.go +++ b/pkg/strategy/irr/draw.go @@ -16,36 +16,44 @@ func (s *Strategy) InitDrawCommands(profit, cumProfit, cumProfitDollar types.Ser canvas := DrawPNL(s.InstanceID(), profit) var buffer bytes.Buffer - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render return in irr") - reply.Message(fmt.Sprintf("[error] cannot render return in irr: %v", err)) + go func() { + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render return in irr") + reply.Message(fmt.Sprintf("[error] cannot render return in irr: %v", err)) + return + } + bbgo.SendPhoto(&buffer) return - } - go bbgo.SendPhoto(&buffer) + }() }) bbgo.RegisterCommand("/nav", "Draw Net Assets Value", func(reply interact.Reply) { canvas := DrawCumPNL(s.InstanceID(), cumProfit) var buffer bytes.Buffer - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render nav in irr") - reply.Message(fmt.Sprintf("[error] canot render nav in irr: %v", err)) + go func() { + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render nav in irr") + reply.Message(fmt.Sprintf("[error] canot render nav in irr: %v", err)) + return + } + bbgo.SendPhoto(&buffer) return - } - go bbgo.SendPhoto(&buffer) + }() }) bbgo.RegisterCommand("/pnl", "Draw Cumulative Profit & Loss", func(reply interact.Reply) { canvas := DrawCumPNL(s.InstanceID(), cumProfitDollar) var buffer bytes.Buffer - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render pnl in irr") - reply.Message(fmt.Sprintf("[error] canot render pnl in irr: %v", err)) + go func() { + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render pnl in irr") + reply.Message(fmt.Sprintf("[error] canot render pnl in irr: %v", err)) + return + } + bbgo.SendPhoto(&buffer) return - } - go bbgo.SendPhoto(&buffer) - + }() }) } From 58bdb9b194c967b1ccee83a44dcf521b999a9b87 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Mon, 17 Oct 2022 22:14:06 +0800 Subject: [PATCH 04/11] strategy:irr remove alpha ranking --- pkg/strategy/irr/strategy.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index f7b14c174..164ac8256 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -412,12 +412,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se rtMrRank = s.rtMr.Rank(s.rtMr.Length()).Last() / float64(s.rtMr.Length()) } s.rtWeight.Update((rtNrRank + rtMrRank) / 2) - rtWeightRank := 0. - if s.rtWeight.Length() >= 100 { - rtWeightRank = s.rtWeight.Rank(s.rtWeight.Length()).Last() / float64(s.rtWeight.Length()) - } - log.Infof("Alpha: %f/1.0", rtWeightRank) - s.rebalancePosition(s.obBuyPrice.Load(), s.obSellPrice.Load(), rtWeightRank) + //rtWeightRank := 0. + //if s.rtWeight.Length() >= 100 { + // rtWeightRank = s.rtWeight.Rank(s.rtWeight.Length()).Last() / float64(s.rtWeight.Length()) + //} + log.Infof("Alpha: %f/1.0", s.rtWeight.Last()) + s.rebalancePosition(s.obBuyPrice.Load(), s.obSellPrice.Load(), s.rtWeight.Last()) } previousRoundTime = currentRoundTime From 7974ee8fd31260fe8804844324c9c96c0a9057b5 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Mon, 17 Oct 2022 22:26:33 +0800 Subject: [PATCH 05/11] strategy:irr: seperate alphas --- config/irr.yaml | 4 ++++ pkg/strategy/irr/strategy.go | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/config/irr.yaml b/config/irr.yaml index 9e8fd56ac..4247efd75 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -19,6 +19,10 @@ exchangeStrategies: hftInterval: 1000 # maxima position in USD amount: 500.0 + # alpha1: negative return reversion + NR: false + # alpha2: moving average reversion + MR: true # Draw pnl drawGraph: true graphPNLPath: "./pnl.png" diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 164ac8256..4b7648d4a 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -48,7 +48,9 @@ type Strategy struct { bbgo.QuantityOrAmount - Interval int `json:"hftInterval"` + Interval int `json:"hftInterval"` + NR bool `json:"NR"` + MR bool `json:"MR"` // for back-test Nrr *NRR @@ -411,7 +413,15 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se if s.rtMr.Length() >= 100 { rtMrRank = s.rtMr.Rank(s.rtMr.Length()).Last() / float64(s.rtMr.Length()) } - s.rtWeight.Update((rtNrRank + rtMrRank) / 2) + 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) //rtWeightRank := 0. //if s.rtWeight.Length() >= 100 { // rtWeightRank = s.rtWeight.Rank(s.rtWeight.Length()).Last() / float64(s.rtWeight.Length()) From 42d87adeecba0f3303e2a25310aaecc483e349a8 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Tue, 18 Oct 2022 02:29:31 +0800 Subject: [PATCH 06/11] strategy:irr: rollback to interval time ticker --- config/irr.yaml | 4 +- pkg/strategy/irr/strategy.go | 135 +++++++++++++++++++++-------------- 2 files changed, 82 insertions(+), 57 deletions(-) diff --git a/config/irr.yaml b/config/irr.yaml index 4247efd75..a9f09128b 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -16,11 +16,11 @@ exchangeStrategies: irr: symbol: BTCBUSD # in milliseconds(ms) - hftInterval: 1000 + hftInterval: 5 # maxima position in USD amount: 500.0 # alpha1: negative return reversion - NR: false + NR: true # alpha2: moving average reversion MR: true # Draw pnl diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 4b7648d4a..3d8f6c51f 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -3,6 +3,10 @@ package irr import ( "context" "fmt" + "os" + "sync" + "time" + "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/data/tsv" "github.com/c9s/bbgo/pkg/datatype/floats" @@ -11,8 +15,6 @@ import ( "github.com/c9s/bbgo/pkg/types" "github.com/sirupsen/logrus" "go.uber.org/atomic" - "os" - "sync" ) const ID = "irr" @@ -58,6 +60,8 @@ type Strategy struct { // realtime book ticker to submit order obBuyPrice *atomic.Float64 obSellPrice *atomic.Float64 + // for getting close price + currentTradePrice *atomic.Float64 // for negative return rate openPrice float64 closePrice float64 @@ -369,11 +373,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.rtWeight = types.NewQueue(100) - currentRoundTime := int64(0) - previousRoundTime := int64(0) - - currentTradePrice := 0. - previousTradePrice := 0. + s.currentTradePrice = atomic.NewFloat64(0) if !bbgo.IsBackTesting { @@ -384,55 +384,80 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se }) s.session.MarketDataStream.OnAggTrade(func(trade types.Trade) { - // rounding to 1000 milliseconds if hftInterval is set to 1000 - currentRoundTime = trade.Time.UnixMilli() % int64(s.Interval) - currentTradePrice = trade.Price.Float64() - if currentRoundTime < previousRoundTime { - - s.openPrice = s.closePrice - // D0 strategy can use now data - // D1 strategy only use previous data (we're here) - s.closePrice = previousTradePrice - //log.Infof("Previous Close Price: %f", s.closePrice) - //log.Infof("Previous Open Price: %f", s.openPrice) - log.Infof("Now Open Price: %f", currentTradePrice) - s.orderExecutor.CancelNoWait(ctx) - // calculate real-time Negative Return - s.rtNr.Update(s.openPrice - s.closePrice) - // calculate real-time Negative Return Rank - rtNrRank := 0. - if s.rtNr.Length() >= 100 { - rtNrRank = s.rtNr.Rank(s.rtNr.Length()).Last() / float64(s.rtNr.Length()) - } - // 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() >= 100 { - 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) - //rtWeightRank := 0. - //if s.rtWeight.Length() >= 100 { - // rtWeightRank = s.rtWeight.Rank(s.rtWeight.Length()).Last() / float64(s.rtWeight.Length()) - //} - log.Infof("Alpha: %f/1.0", s.rtWeight.Last()) - s.rebalancePosition(s.obBuyPrice.Load(), s.obSellPrice.Load(), s.rtWeight.Last()) - } - - previousRoundTime = currentRoundTime - previousTradePrice = currentTradePrice + s.currentTradePrice = atomic.NewFloat64(trade.Price.Float64()) }) + + go func() { + intervalCloseTicker := time.NewTicker(time.Duration(s.Interval) * time.Millisecond) + defer intervalCloseTicker.Stop() + + for { + select { + case <-intervalCloseTicker.C: + if s.currentTradePrice.Load() > 0 { + 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()) + } + // 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()) + s.orderExecutor.CancelNoWait(context.Background()) + } + case <-s.stopC: + log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) + return + + case <-ctx.Done(): + log.Warnf("%s goroutine stopped, due to the cancelled context", s.Symbol) + return + } + } + }() + + go func() { + intervalOpenTicker := time.NewTicker(time.Duration(s.Interval) * time.Millisecond) + defer intervalOpenTicker.Stop() + for { + select { + case <-intervalOpenTicker.C: + time.Sleep(200 * time.Microsecond) + if s.currentTradePrice.Load() > 0 { + s.openPrice = s.currentTradePrice.Load() + //log.Infof("Open Price: %f", s.openPrice) + } + case <-s.stopC: + log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) + return + + case <-ctx.Done(): + log.Warnf("%s goroutine stopped, due to the cancelled context", s.Symbol) + return + } + } + }() } bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) { From 303e2c8413f9cd8bb37b89df60711cdf291ac2a7 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Tue, 18 Oct 2022 05:59:47 +0800 Subject: [PATCH 07/11] strategy:irr: redesign to maker strategy --- config/irr.yaml | 8 +++-- pkg/strategy/irr/draw.go | 42 +++++++++++--------------- pkg/strategy/irr/strategy.go | 58 ++++++++++++++---------------------- 3 files changed, 46 insertions(+), 62 deletions(-) diff --git a/config/irr.yaml b/config/irr.yaml index a9f09128b..8fa9ac329 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -17,8 +17,12 @@ exchangeStrategies: symbol: BTCBUSD # in milliseconds(ms) hftInterval: 5 - # maxima position in USD - amount: 500.0 + # indicator window + window: 100 + # limit maker order quantity + quantity: 0.01 + # bonus spread in USD + spread: 0.25 # 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 b4346b815..b038ec702 100644 --- a/pkg/strategy/irr/draw.go +++ b/pkg/strategy/irr/draw.go @@ -16,44 +16,38 @@ func (s *Strategy) InitDrawCommands(profit, cumProfit, cumProfitDollar types.Ser canvas := DrawPNL(s.InstanceID(), profit) var buffer bytes.Buffer - go func() { - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render return in irr") - reply.Message(fmt.Sprintf("[error] cannot render return in irr: %v", err)) - return - } - bbgo.SendPhoto(&buffer) + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render return in irr") + reply.Message(fmt.Sprintf("[error] cannot render return in irr: %v", err)) return - }() + } + bbgo.SendPhoto(&buffer) + return }) bbgo.RegisterCommand("/nav", "Draw Net Assets Value", func(reply interact.Reply) { canvas := DrawCumPNL(s.InstanceID(), cumProfit) var buffer bytes.Buffer - go func() { - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render nav in irr") - reply.Message(fmt.Sprintf("[error] canot render nav in irr: %v", err)) - return - } - bbgo.SendPhoto(&buffer) + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render nav in irr") + reply.Message(fmt.Sprintf("[error] canot render nav in irr: %v", err)) return - }() + } + bbgo.SendPhoto(&buffer) + return }) bbgo.RegisterCommand("/pnl", "Draw Cumulative Profit & Loss", func(reply interact.Reply) { canvas := DrawCumPNL(s.InstanceID(), cumProfitDollar) var buffer bytes.Buffer - go func() { - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render pnl in irr") - reply.Message(fmt.Sprintf("[error] canot render pnl in irr: %v", err)) - return - } - bbgo.SendPhoto(&buffer) + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render pnl in irr") + reply.Message(fmt.Sprintf("[error] canot render pnl in irr: %v", err)) return - }() + } + bbgo.SendPhoto(&buffer) + return }) } diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 3d8f6c51f..68ac52961 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -49,6 +49,7 @@ type Strategy struct { orderExecutor *bbgo.GeneralOrderExecutor bbgo.QuantityOrAmount + Spread float64 `json:"spread"` Interval int `json:"hftInterval"` NR bool `json:"NR"` @@ -365,13 +366,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // s.Ma.LoadK((*klines)[0:]) //} - s.rtNr = types.NewQueue(100) + s.rtNr = types.NewQueue(s.Window) s.rtMaFast = types.NewQueue(1) s.rtMaSlow = types.NewQueue(5) - s.rtMr = types.NewQueue(100) + s.rtMr = types.NewQueue(s.Window) - s.rtWeight = types.NewQueue(100) + s.rtWeight = types.NewQueue(s.Window) s.currentTradePrice = atomic.NewFloat64(0) @@ -395,6 +396,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se select { case <-intervalCloseTicker.C: 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 @@ -424,7 +426,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.rtWeight.Update(alpha) log.Infof("Alpha: %f/1.0", s.rtWeight.Last()) s.rebalancePosition(s.obBuyPrice.Load(), s.obSellPrice.Load(), s.rtWeight.Last()) - s.orderExecutor.CancelNoWait(context.Background()) } case <-s.stopC: log.Warnf("%s goroutine stopped, due to the stop signal", s.Symbol) @@ -443,7 +444,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se for { select { case <-intervalOpenTicker.C: - time.Sleep(200 * time.Microsecond) + time.Sleep(50 * time.Microsecond) if s.currentTradePrice.Load() > 0 { s.openPrice = s.currentTradePrice.Load() //log.Infof("Open Price: %f", s.openPrice) @@ -487,44 +488,29 @@ func (s *Strategy) CalcAssetValue(price fixedpoint.Value) fixedpoint.Value { } func (s *Strategy) rebalancePosition(bestBid, bestAsk float64, w float64) { - // alpha-weighted assets (inventory and capital) - position := s.orderExecutor.Position() - p := fixedpoint.NewFromFloat((bestBid + bestAsk) / 2) - - targetBase := s.QuantityOrAmount.CalculateQuantity(p).Mul(fixedpoint.NewFromFloat(w)) - - // to buy/sell quantity - diffQty := targetBase.Sub(position.Base) - log.Infof("Target Position Diff: %f", diffQty.Float64()) - - // ignore small changes - if diffQty.Abs().Float64() < 0.0005 { - return - } - - if diffQty.Sign() > 0 { - _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ + if w < 0.5 { + _, errB := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ Symbol: s.Symbol, Side: types.SideTypeBuy, - Quantity: diffQty.Abs(), - Type: types.OrderTypeLimit, - Price: fixedpoint.NewFromFloat(bestBid), - Tag: "irr re-balance: buy", + Quantity: s.Quantity, + Type: types.OrderTypeLimitMaker, + Price: fixedpoint.NewFromFloat(bestBid - s.Spread), + Tag: "irr short: buy", }) - if err != nil { - log.WithError(err) + if errB != nil { + log.WithError(errB) } - } else if diffQty.Sign() < 0 { - _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ + } else if w > 0.5 { + _, errA := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ Symbol: s.Symbol, Side: types.SideTypeSell, - Quantity: diffQty.Abs(), - Type: types.OrderTypeLimit, - Price: fixedpoint.NewFromFloat(bestAsk), - Tag: "irr re-balance: sell", + Quantity: s.Quantity, + Type: types.OrderTypeLimitMaker, + Price: fixedpoint.NewFromFloat(bestAsk + s.Spread), + Tag: "irr long: buy", }) - if err != nil { - log.WithError(err) + if errA != nil { + log.WithError(errA) } } } From 612261c48c0c8df7306d19ab56b4a3a28aac68c0 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Wed, 19 Oct 2022 16:02:00 +0800 Subject: [PATCH 08/11] 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) - } - } -} From 614209e9fd4f3837b578bc2c6bc5fe5a8a6a98fa Mon Sep 17 00:00:00 2001 From: austin362667 Date: Wed, 19 Oct 2022 17:10:33 +0800 Subject: [PATCH 09/11] strategy:irr fix kline time syncing --- pkg/strategy/irr/strategy.go | 104 +++++++++++------------------------ 1 file changed, 32 insertions(+), 72 deletions(-) diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index c75c9e29a..8eb010440 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "sync" + "sync/atomic" "time" "github.com/c9s/bbgo/pkg/bbgo" @@ -14,7 +15,6 @@ import ( "github.com/c9s/bbgo/pkg/indicator" "github.com/c9s/bbgo/pkg/types" "github.com/sirupsen/logrus" - "go.uber.org/atomic" ) const ID = "irr" @@ -58,26 +58,15 @@ type Strategy struct { Nrr *NRR Ma *indicator.SMA // realtime book ticker to submit order - obBuyPrice *atomic.Float64 - obSellPrice *atomic.Float64 - // for posting LO - canBuy bool - canSell bool + obBuyPrice uint64 + obSellPrice uint64 // for getting close price - currentTradePrice *atomic.Float64 - tradePriceSeries *types.Queue + currentTradePrice uint64 // for negative return rate openPrice float64 closePrice float64 - rtNr *types.Queue - // for moving average reversion - rtMaFast *types.Queue - rtMaSlow *types.Queue - rtMr *types.Queue - // for final alpha (Nr+Mr)/2 - rtWeight *types.Queue - stopC chan struct{} + stopC chan struct{} // StrategyController bbgo.StrategyController @@ -346,14 +335,6 @@ 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) @@ -364,36 +345,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.orderExecutor.Bind() s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol) - //back-test only, because 1s delayed a lot - //kLineStore, _ := s.session.MarketDataStore(s.Symbol) - //s.Nrr = &NRR{IntervalWindow: types.IntervalWindow{Window: 2, Interval: s.Interval}, RankingWindow: s.Window} - //s.Nrr.BindK(s.session.MarketDataStream, s.Symbol, s.Interval) - //if klines, ok := kLineStore.KLinesOfInterval(s.Nrr.Interval); ok { - // s.Nrr.LoadK((*klines)[0:]) - //} - //s.Ma = &indicator.SMA{IntervalWindow: types.IntervalWindow{Window: s.Window, Interval: s.Interval}} - //s.Ma.BindK(s.session.MarketDataStream, s.Symbol, s.Interval) - //if klines, ok := kLineStore.KLinesOfInterval(s.Ma.Interval); ok { - // s.Ma.LoadK((*klines)[0:]) - //} - - s.rtNr = types.NewQueue(s.Window) - - s.rtMaFast = types.NewQueue(1) - s.rtMaSlow = types.NewQueue(5) - s.rtMr = types.NewQueue(s.Window) - - s.rtWeight = types.NewQueue(s.Window) - - 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) + atomic.SwapUint64(&s.currentTradePrice, 0.) + s.closePrice = 0. + s.openPrice = 0. klinDirections := types.NewQueue(100) started := false boxOpenPrice := 0. @@ -404,40 +358,43 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.session.MarketDataStream.OnBookTickerUpdate(func(bt types.BookTicker) { // quote order book price - s.obBuyPrice = atomic.NewFloat64(bt.Buy.Float64()) - s.obSellPrice = atomic.NewFloat64(bt.Sell.Float64()) + newBid := uint64(bt.Buy.Float64()) + newAsk := uint64(bt.Sell.Float64()) + atomic.SwapUint64(&s.obBuyPrice, newBid) + atomic.SwapUint64(&s.obSellPrice, newAsk) }) s.session.MarketDataStream.OnAggTrade(func(trade types.Trade) { - s.currentTradePrice = atomic.NewFloat64(trade.Price.Float64()) + tradePrice := uint64(trade.Price.Float64()) + atomic.SwapUint64(&s.currentTradePrice, tradePrice) }) + closeTime := <-time.After(time.Duration(s.Interval-int(time.Now().UnixMilli())%s.Interval) * time.Millisecond) + log.Infof("kline close timing synced @ %s", closeTime.Format("2006-01-02 15:04:05.000000")) 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: + log.Infof("kline close time @ %s", time.Now().Format("2006-01-02 15:04:05.000000")) s.orderExecutor.CancelNoWait(context.Background()) - if s.currentTradePrice.Load() > 0 { - s.closePrice = s.currentTradePrice.Load() - log.Infof("Close Price: %f", s.closePrice) + if s.currentTradePrice > 0 { + s.closePrice = float64(s.currentTradePrice) + log.Infof("Close Price: %f", float64(s.closePrice)) if s.closePrice > 0 && s.openPrice > 0 { direction := s.closePrice - s.openPrice - klinDirections.Update(direction) + klinDirections.Update(float64(direction)) regimeShift := klinDirections.Index(0)*klinDirections.Index(1) < 0 if regimeShift && !started { - boxOpenPrice = s.openPrice + boxOpenPrice = float64(s.openPrice) started = true boxCounter = 0 log.Infof("box started at price: %f", boxOpenPrice) } else if regimeShift && started { - boxClosePrice = s.openPrice + boxClosePrice = float64(s.openPrice) started = false log.Infof("box ended at price: %f with time length: %d", boxClosePrice, boxCounter) // box ending, should re-balance position @@ -451,7 +408,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se Side: types.SideTypeSell, Quantity: s.Quantity, Type: types.OrderTypeLimitMaker, - Price: fixedpoint.NewFromFloat(s.obSellPrice.Load()), + Price: fixedpoint.NewFromFloat(float64(s.obSellPrice)), Tag: "irr re-balance: sell", }) if err != nil { @@ -463,7 +420,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se Side: types.SideTypeBuy, Quantity: s.Quantity, Type: types.OrderTypeLimitMaker, - Price: fixedpoint.NewFromFloat(s.obBuyPrice.Load()), + Price: fixedpoint.NewFromFloat(float64(s.obBuyPrice)), Tag: "irr re-balance: buy", }) if err != nil { @@ -484,18 +441,22 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } } + }() + openTime := <-time.After(time.Duration(s.Interval-int(time.Now().UnixMilli())%s.Interval) * time.Millisecond) + log.Infof("kline open timing synced @ %s", openTime.Format("2006-01-02 15:04:05.000000")) 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(time.Duration(s.Interval/10) * time.Millisecond) - if s.currentTradePrice.Load() > 0 && s.closePrice > 0 { - s.openPrice = s.currentTradePrice.Load() + log.Infof("kline open time @ %s", time.Now().Format("2006-01-02 15:04:05.000000")) + + if s.currentTradePrice > 0 && s.closePrice > 0 { + s.openPrice = float64(s.currentTradePrice) log.Infof("Open Price: %f", s.openPrice) } case <-s.stopC: @@ -527,7 +488,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se _, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String()) _ = s.orderExecutor.GracefulCancel(ctx) }) - return nil } From 778a3d8be100143b5e838e391c5b30dcc6b20621 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Wed, 19 Oct 2022 17:24:27 +0800 Subject: [PATCH 10/11] strategy:irr: clean up strategy:irr: clean up strategy:irr: clean up strategy:irr: clean up --- config/irr.yaml | 12 ++---------- pkg/strategy/irr/strategy.go | 18 +++++------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/config/irr.yaml b/config/irr.yaml index eb899c093..302980d28 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -22,18 +22,10 @@ exchangeStrategies: irr: symbol: BTCBUSD # in milliseconds(ms) + # must > 10 ms hftInterval: 1000 - # indicator window - window: 0 - # maxima position in USD - amount: 100 + # qty per trade quantity: 0.001 - # minProfit pips in USD - pips: 0.0 - # alpha1: negative return reversion - NR: true - # alpha2: moving average reversion - MR: true # Draw pnl drawGraph: true graphPNLPath: "./pnl.png" diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 8eb010440..dbdf30c82 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -48,15 +48,9 @@ type Strategy struct { orderExecutor *bbgo.GeneralOrderExecutor bbgo.QuantityOrAmount - MinProfit float64 `json:"pips"` - Interval int `json:"hftInterval"` - NR bool `json:"NR"` - MR bool `json:"MR"` + Interval int `json:"hftInterval"` - // for back-test - Nrr *NRR - Ma *indicator.SMA // realtime book ticker to submit order obBuyPrice uint64 obSellPrice uint64 @@ -399,9 +393,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se 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()) + log.Infof("Alpha: %f", nirr.Float64()) if nirr.Float64() < 0 { _, err := s.orderExecutor.SubmitOrders(context.Background(), types.SubmitOrder{ Symbol: s.Symbol, @@ -409,7 +401,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se Quantity: s.Quantity, Type: types.OrderTypeLimitMaker, Price: fixedpoint.NewFromFloat(float64(s.obSellPrice)), - Tag: "irr re-balance: sell", + Tag: "irrSell", }) if err != nil { log.WithError(err) @@ -421,7 +413,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se Quantity: s.Quantity, Type: types.OrderTypeLimitMaker, Price: fixedpoint.NewFromFloat(float64(s.obBuyPrice)), - Tag: "irr re-balance: buy", + Tag: "irrBuy", }) if err != nil { log.WithError(err) @@ -452,7 +444,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se for { select { case <-intervalOpenTicker.C: - time.Sleep(time.Duration(s.Interval/10) * time.Millisecond) + time.Sleep(10 * time.Millisecond) log.Infof("kline open time @ %s", time.Now().Format("2006-01-02 15:04:05.000000")) if s.currentTradePrice > 0 && s.closePrice > 0 { From 6e29359c858147ed317eb60627e1031a9b64a80c Mon Sep 17 00:00:00 2001 From: austin362667 Date: Wed, 19 Oct 2022 22:08:44 +0800 Subject: [PATCH 11/11] strategy:irr: fix logical error --- pkg/strategy/irr/strategy.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index dbdf30c82..a155eb3f8 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -377,18 +377,18 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se if s.currentTradePrice > 0 { s.closePrice = float64(s.currentTradePrice) - log.Infof("Close Price: %f", float64(s.closePrice)) + log.Infof("Close Price: %f", s.closePrice) if s.closePrice > 0 && s.openPrice > 0 { direction := s.closePrice - s.openPrice - klinDirections.Update(float64(direction)) + klinDirections.Update(direction) regimeShift := klinDirections.Index(0)*klinDirections.Index(1) < 0 if regimeShift && !started { - boxOpenPrice = float64(s.openPrice) + boxOpenPrice = s.openPrice started = true boxCounter = 0 log.Infof("box started at price: %f", boxOpenPrice) } else if regimeShift && started { - boxClosePrice = float64(s.openPrice) + boxClosePrice = s.closePrice started = false log.Infof("box ended at price: %f with time length: %d", boxClosePrice, boxCounter) // box ending, should re-balance position