From ce86544c43c76acc051d3bbf27016eae39ead168 Mon Sep 17 00:00:00 2001 From: zenix Date: Thu, 20 Oct 2022 20:03:10 +0900 Subject: [PATCH] optimize: drift strategy to use market trade signals --- config/drift.yaml | 102 --------- config/driftBTC.yaml | 27 ++- pkg/strategy/drift/strategy.go | 301 ++++++++++++--------------- pkg/strategy/elliottwave/strategy.go | 2 +- 4 files changed, 145 insertions(+), 287 deletions(-) delete mode 100644 config/drift.yaml diff --git a/config/drift.yaml b/config/drift.yaml deleted file mode 100644 index 0ace1ba49..000000000 --- a/config/drift.yaml +++ /dev/null @@ -1,102 +0,0 @@ ---- -persistence: - redis: - host: 127.0.0.1 - port: 6379 - db: 0 - -sessions: - binance: - exchange: binance - futures: false - envVarPrefix: binance - heikinAshi: false - - # Drift strategy intends to place buy/sell orders as much value mas it could be. To exchanges that requires to - # calculate fees before placing limit orders (e.g. FTX Pro), make sure the fee rate is configured correctly and - # enable modifyOrderAmountForFee to prevent order rejection. - makerFeeRate: 0.0002 - takerFeeRate: 0.0007 - modifyOrderAmountForFee: false - -exchangeStrategies: - -- on: binance - drift: - canvasPath: "./output.png" - symbol: ETHBUSD - limitOrder: false - quantity: 0.01 - # kline interval for indicators - interval: 1m - window: 1 - useAtr: true - useStopLoss: true - stoploss: 0.23% - source: ohlc4 - predictOffset: 2 - noTrailingStopLoss: false - trailingStopLossType: kline - # stddev on high/low-source - hlVarianceMultiplier: 0.13 - hlRangeWindow: 4 - smootherWindow: 19 - fisherTransformWindow: 73 - atrWindow: 14 - # orders not been traded will be canceled after `pendingMinutes` minutes - pendingMinutes: 5 - noRebalance: true - trendWindow: 12 - rebalanceFilter: 2 - - trailingActivationRatio: [0.0015, 0.002, 0.004, 0.01] - trailingCallbackRate: [0.0001, 0.00012, 0.001, 0.002] - - generateGraph: true - graphPNLDeductFee: true - graphPNLPath: "./pnl.png" - graphCumPNLPath: "./cumpnl.png" - #exits: - #- roiStopLoss: - # percentage: 0.8% - #- roiTakeProfit: - # percentage: 35% - #- protectiveStopLoss: - # activationRatio: 0.6% - # stopLossRatio: 0.1% - # placeStopOrder: false - #- protectiveStopLoss: - # activationRatio: 5% - # stopLossRatio: 1% - # placeStopOrder: false - #- cumulatedVolumeTakeProfit: - # interval: 5m - # window: 2 - # minQuoteVolume: 200_000_000 - #- protectiveStopLoss: - # activationRatio: 2% - # stopLossRatio: 1% - # placeStopOrder: false - -sync: - userDataStream: - trades: true - filledOrders: true - sessions: - - binance - symbols: - - ETHBUSD - -backtest: - startTime: "2022-09-25" - endTime: "2022-09-30" - symbols: - - ETHBUSD - sessions: [binance] - accounts: - binance: - makerFeeRate: 0.0000 - takerFeeRate: 0.0000 - balances: - ETH: 0.03 - BUSD: 0 diff --git a/config/driftBTC.yaml b/config/driftBTC.yaml index ffe973367..898bbd4a2 100644 --- a/config/driftBTC.yaml +++ b/config/driftBTC.yaml @@ -26,6 +26,7 @@ exchangeStrategies: - on: binance drift: + debug: false minInterval: 1s limitOrder: true #quantity: 0.0012 @@ -33,26 +34,24 @@ exchangeStrategies: symbol: BTCUSDT # kline interval for indicators interval: 1s - window: 6 + window: 2 useAtr: true useStopLoss: true - stoploss: 0.05% + stoploss: 0.01% source: hl2 predictOffset: 2 - noTrailingStopLoss: false - trailingStopLossType: kline + noTrailingStopLoss: true # stddev on high/low-source - hlVarianceMultiplier: 0.14 - hlRangeWindow: 4 - smootherWindow: 3 - fisherTransformWindow: 125 - #fisherTransformWindow: 117 + hlVarianceMultiplier: 0.7 + hlRangeWindow: 6 + smootherWindow: 10 + fisherTransformWindow: 45 atrWindow: 24 # orders not been traded will be canceled after `pendingMinutes` minutes - pendingMinutes: 10 + pendingMinInterval: 6 noRebalance: true - trendWindow: 15 - rebalanceFilter: -0.1 + trendWindow: 4 + rebalanceFilter: 2 # ActivationRatio should be increasing order # when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop @@ -126,8 +125,8 @@ sync: - BTCUSDT backtest: - startTime: "2022-10-18" - endTime: "2022-10-19" + startTime: "2022-10-19" + endTime: "2022-10-20" symbols: - BTCUSDT sessions: [binance] diff --git a/pkg/strategy/drift/strategy.go b/pkg/strategy/drift/strategy.go index 40d62c24b..cf4287c87 100644 --- a/pkg/strategy/drift/strategy.go +++ b/pkg/strategy/drift/strategy.go @@ -66,57 +66,58 @@ type Strategy struct { *types.TradeStats `persistence:"trade_stats"` p *types.Position - MinInterval types.Interval `json:"MinInterval"` + MinInterval types.Interval `json:"MinInterval"` // minimum interval referred for doing stoploss/trailing exists and updating highest/lowest - priceLines *types.Queue - trendLine types.UpdatableSeriesExtend - ma types.UpdatableSeriesExtend - stdevHigh *indicator.StdDev - stdevLow *indicator.StdDev - drift *DriftMA - atr *indicator.ATR - midPrice fixedpoint.Value - lock sync.RWMutex `ignore:"true"` - positionLock sync.RWMutex `ignore:"true"` - startTime time.Time - counter int - orderPendingCounter map[uint64]int - frameKLine *types.KLine - klineMin *types.KLine + priceLines *types.Queue + trendLine types.UpdatableSeriesExtend + ma types.UpdatableSeriesExtend + stdevHigh *indicator.StdDev + stdevLow *indicator.StdDev + drift *DriftMA + atr *indicator.ATR + midPrice fixedpoint.Value // the midPrice is the average of bestBid and bestAsk in public orderbook + lock sync.RWMutex `ignore:"true"` // lock for midPrice + positionLock sync.RWMutex `ignore:"true"` // lock for highest/lowest and p + startTime time.Time // trading start time + counter int // number of MinInterval since startTime + maxCounterBuyCanceled int // the largest counter of the order on the buy side been cancelled. meaning the latest cancelled buy order. + maxCounterSellCanceled int // the largest counter of the order on the sell side been cancelled. meaning the latest cancelled sell order. + orderPendingCounter map[uint64]int // records the timepoint when the orders are created, using the counter at the time. + frameKLine *types.KLine // last kline in Interval + klineMin *types.KLine // last kline in MinInterval - beta float64 + beta float64 // last beta value from trendline's linear regression (previous slope of the trendline) - UseStopLoss bool `json:"useStopLoss" modifiable:"true"` - UseAtr bool `json:"useAtr" modifiable:"true"` - StopLoss fixedpoint.Value `json:"stoploss" modifiable:"true"` - CanvasPath string `json:"canvasPath"` - PredictOffset int `json:"predictOffset"` - HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier" modifiable:"true"` - NoTrailingStopLoss bool `json:"noTrailingStopLoss" modifiable:"true"` - TrailingStopLossType string `json:"trailingStopLossType" modifiable:"true"` // trailing stop sources. Possible options are `kline` for 1m kline and `realtime` from order updates - HLRangeWindow int `json:"hlRangeWindow"` - SmootherWindow int `json:"smootherWindow"` - FisherTransformWindow int `json:"fisherTransformWindow"` - ATRWindow int `json:"atrWindow"` - PendingMinInterval int `json:"pendingMinInterval" modifiable:"true"` // if order not be traded for pendingMinInterval of time, cancel it. - NoRebalance bool `json:"noRebalance" modifiable:"true"` // disable rebalance - TrendWindow int `json:"trendWindow"` // trendLine is used for rebalancing the position. When trendLine goes up, hold base, otherwise hold quote - RebalanceFilter float64 `json:"rebalanceFilter" modifiable:"true"` // beta filter on the Linear Regression of trendLine + Debug bool `json:"debug" modifiable:"true"` // to print debug message or not + UseStopLoss bool `json:"useStopLoss" modifiable:"true"` // whether to use stoploss rate to do stoploss + UseAtr bool `json:"useAtr" modifiable:"true"` // use atr as stoploss + StopLoss fixedpoint.Value `json:"stoploss" modifiable:"true"` // stoploss rate + PredictOffset int `json:"predictOffset"` // the lookback length for the prediction using linear regression + HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier" modifiable:"true"` // modifier to set the limit order price + NoTrailingStopLoss bool `json:"noTrailingStopLoss" modifiable:"true"` // turn off the trailing exit and stoploss + HLRangeWindow int `json:"hlRangeWindow"` // ma window for kline high/low changes + SmootherWindow int `json:"smootherWindow"` // window that controls the smoothness of drift + FisherTransformWindow int `json:"fisherTransformWindow"` // fisher transform window to filter drift's negative signals + ATRWindow int `json:"atrWindow"` // window for atr indicator + PendingMinInterval int `json:"pendingMinInterval" modifiable:"true"` // if order not be traded for pendingMinInterval of time, cancel it. + NoRebalance bool `json:"noRebalance" modifiable:"true"` // disable rebalance + TrendWindow int `json:"trendWindow"` // trendLine is used for rebalancing the position. When trendLine goes up, hold base, otherwise hold quote + RebalanceFilter float64 `json:"rebalanceFilter" modifiable:"true"` // beta filter on the Linear Regression of trendLine TrailingCallbackRate []float64 `json:"trailingCallbackRate" modifiable:"true"` TrailingActivationRatio []float64 `json:"trailingActivationRatio" modifiable:"true"` - buyPrice float64 `persistence:"buy_price"` - sellPrice float64 `persistence:"sell_price"` - highestPrice float64 `persistence:"highest_price"` - lowestPrice float64 `persistence:"lowest_price"` + buyPrice float64 `persistence:"buy_price"` // price when a long position is opened + sellPrice float64 `persistence:"sell_price"` // price when a short position is opened + highestPrice float64 `persistence:"highest_price"` // highestPrice when the position is opened + lowestPrice float64 `persistence:"lowest_price"` // lowestPrice when the position is opened // This is not related to trade but for statistics graph generation // Will deduct fee in percentage from every trade GraphPNLDeductFee bool `json:"graphPNLDeductFee"` - GraphPNLPath string `json:"graphPNLPath"` - GraphCumPNLPath string `json:"graphCumPNLPath"` - // Whether to generate graph when shutdown - GenerateGraph bool `json:"generateGraph"` + CanvasPath string `json:"canvasPath"` // backtest related. the path to store the indicator graph + GraphPNLPath string `json:"graphPNLPath"` // backtest related. the path to store the pnl % graph per trade graph. + GraphCumPNLPath string `json:"graphCumPNLPath"` // backtest related. the path to store the asset changes in graph + GenerateGraph bool `json:"generateGraph"` // whether to generate graph when shutdown ExitMethods bbgo.ExitMethodSet `json:"exits"` Session *bbgo.ExchangeSession @@ -139,7 +140,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { if !bbgo.IsBackTesting { session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{}) - session.Subscribe(types.AggTradeChannel, s.Symbol, types.SubscribeOptions{}) + session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{}) // able to preload if s.MinInterval.Milliseconds() >= types.Interval1s.Milliseconds() && s.MinInterval.Milliseconds()%types.Interval1s.Milliseconds() == 0 { maxWindow := (s.Window + s.SmootherWindow + s.FisherTransformWindow) * (s.Interval.Milliseconds() / s.MinInterval.Milliseconds()) @@ -296,6 +297,15 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, e err := s.GeneralOrderExecutor.CancelNoWait(ctx) // TODO: clean orderPendingCounter on cancel/trade for _, order := range nonTraded { + if order.Side == types.SideTypeSell { + if s.maxCounterSellCanceled < s.orderPendingCounter[order.OrderID] { + s.maxCounterSellCanceled = s.orderPendingCounter[order.OrderID] + } + } else { + if s.maxCounterBuyCanceled < s.orderPendingCounter[order.OrderID] { + s.maxCounterBuyCanceled = s.orderPendingCounter[order.OrderID] + } + } delete(s.orderPendingCounter, order.OrderID) } log.Warnf("cancel all %v", err) @@ -346,7 +356,6 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) { bestBid := ticker.Buy bestAsk := ticker.Sell - var pricef float64 if !util.TryLock(&s.lock) { return } @@ -357,37 +366,10 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) { } else { s.midPrice = bestBid } - pricef = s.midPrice.Float64() - s.lock.Unlock() - if !util.TryLock(&s.positionLock) { - return - } + // we removed realtime stoploss and trailingStop. - if s.highestPrice > 0 && s.highestPrice < pricef { - s.highestPrice = pricef - } - if s.lowestPrice > 0 && s.lowestPrice > pricef { - s.lowestPrice = pricef - } - if s.CheckStopLoss() { - s.positionLock.Unlock() - s.ClosePosition(ctx, fixedpoint.One) - return - } - // for trailing stoploss during the realtime - if s.NoTrailingStopLoss || s.TrailingStopLossType == "kline" { - s.positionLock.Unlock() - return - } - - exitCondition := s.trailingCheck(pricef, "short") || s.trailingCheck(pricef, "long") - - s.positionLock.Unlock() - if exitCondition { - s.ClosePosition(ctx, fixedpoint.One) - } }) s.getLastPrice = func() (lastPrice fixedpoint.Value) { var ok bool @@ -585,35 +567,29 @@ func (s *Strategy) klineHandlerMin(ctx context.Context, kline types.KLine) { if s.highestPrice > 0 && highf > s.highestPrice { s.highestPrice = highf } + s.positionLock.Unlock() numPending := 0 var err error if numPending, err = s.smartCancel(ctx, pricef, atr); err != nil { log.WithError(err).Errorf("cannot cancel orders") - s.positionLock.Unlock() return } if numPending > 0 { - s.positionLock.Unlock() return } - if s.NoTrailingStopLoss || s.TrailingStopLossType == "realtime" { - s.positionLock.Unlock() + if s.NoTrailingStopLoss { return } exitCondition := s.CheckStopLoss() || s.trailingCheck(highf, "short") || s.trailingCheck(lowf, "long") - s.positionLock.Unlock() if exitCondition { _ = s.ClosePosition(ctx, fixedpoint.One) } } func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { - var driftPred, atr float64 - var drift []float64 - s.frameKLine.Set(&kline) source := s.GetSource(&kline) @@ -625,9 +601,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { s.atr.PushK(kline) - driftPred = s.drift.Predict(s.PredictOffset) - ddriftPred := s.drift.drift.Predict(s.PredictOffset) - atr = s.atr.Last() + atr := s.atr.Last() price := s.getLastPrice() pricef := price.Float64() lowf := math.Min(kline.Low.Float64(), pricef) @@ -636,7 +610,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { s.stdevLow.Update(lowdiff) highdiff := highf - s.ma.Last() s.stdevHigh.Update(highdiff) - drift = s.drift.Array(2) + drift := s.drift.Array(2) if len(drift) < 2 || len(drift) < s.PredictOffset { return } @@ -649,77 +623,78 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { return } - s.positionLock.Lock() log.Infof("highdiff: %3.2f ma: %.2f, open: %8v, close: %8v, high: %8v, low: %8v, time: %v %v", s.stdevHigh.Last(), s.ma.Last(), kline.Open, kline.Close, kline.High, kline.Low, kline.StartTime, kline.EndTime) + + s.positionLock.Lock() if s.lowestPrice > 0 && lowf < s.lowestPrice { s.lowestPrice = lowf } if s.highestPrice > 0 && highf > s.highestPrice { s.highestPrice = highf } + s.positionLock.Unlock() if !s.NoRebalance { s.Rebalance(ctx) } - balances := s.GeneralOrderExecutor.Session().GetAccount().Balances() - bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, ddriftPred: %.4f, drift[1]: %.4f, ddrift[1]: %.4f, atr: %.4f, lowf %.4f, highf: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f", - sourcef, pricef, driftPred, ddriftPred, drift[1], ddrift[1], atr, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice) - // Notify will parse args to strings and process separately - bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s", - s.CalcAssetValue(price), - s.Market.QuoteCurrency, - balances[s.Market.BaseCurrency].String(), - balances[s.Market.BaseCurrency].Total().Mul(price), - s.Market.QuoteCurrency, - balances[s.Market.QuoteCurrency].String(), - ) + if s.Debug { + balances := s.GeneralOrderExecutor.Session().GetAccount().Balances() + bbgo.Notify("source: %.4f, price: %.4f, drift[0]: %.4f, ddrift[0]: %.4f, lowf %.4f, highf: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f", + sourcef, pricef, drift[0], ddrift[0], atr, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice) + // Notify will parse args to strings and process separately + bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s", + s.CalcAssetValue(price), + s.Market.QuoteCurrency, + balances[s.Market.BaseCurrency].String(), + balances[s.Market.BaseCurrency].Total().Mul(price), + s.Market.QuoteCurrency, + balances[s.Market.QuoteCurrency].String(), + ) + } shortCondition := drift[1] >= 0 && drift[0] <= 0 || (drift[1] >= drift[0] && drift[1] <= 0) || ddrift[1] >= 0 && ddrift[0] <= 0 || (ddrift[1] >= ddrift[0] && ddrift[1] <= 0) longCondition := drift[1] <= 0 && drift[0] >= 0 || (drift[1] <= drift[0] && drift[1] >= 0) || ddrift[1] <= 0 && ddrift[0] >= 0 || (ddrift[1] <= ddrift[0] && ddrift[1] >= 0) if shortCondition && longCondition { - if drift[1] > drift[0] { + if s.priceLines.Index(1) > s.priceLines.Last() { longCondition = false } else { shortCondition = false } } - exitCondition := s.CheckStopLoss() || s.trailingCheck(pricef, "short") || s.trailingCheck(pricef, "long") + exitCondition := !s.NoTrailingStopLoss && (s.CheckStopLoss() || s.trailingCheck(pricef, "short") || s.trailingCheck(pricef, "long")) - if exitCondition { - s.positionLock.Unlock() - if err := s.GeneralOrderExecutor.CancelNoWait(ctx); err != nil { + if exitCondition || longCondition || shortCondition { + var err error + var hold int + if hold, err = s.smartCancel(ctx, sourcef, atr); err != nil { log.WithError(err).Errorf("cannot cancel orders") + } + if hold > 0 { return } - _ = s.ClosePosition(ctx, fixedpoint.One) - if shortCondition || longCondition { - s.positionLock.Lock() - } else { - return + } else { + if _, err := s.smartCancel(ctx, sourcef, atr); err != nil { + log.WithError(err).Errorf("cannot cancel orders") } + return } if longCondition { - if err := s.GeneralOrderExecutor.CancelNoWait(ctx); err != nil { - log.WithError(err).Errorf("cannot cancel orders") - s.positionLock.Unlock() - return - } source = source.Sub(fixedpoint.NewFromFloat(s.stdevLow.Last() * s.HighLowVarianceMultiplier)) if source.Compare(price) > 0 { source = price } - /*source = fixedpoint.NewFromFloat(s.ma.Last() - s.stdevLow.Last()*s.HighLowVarianceMultiplier) - if source.Compare(price) > 0 { - source = price - } - sourcef = source.Float64()*/ + log.Infof("source in long %v %v %f", source, price, s.stdevLow.Last()) - s.positionLock.Unlock() opt := s.OpenPositionOptions opt.Long = true + opt.LimitOrder = true + // force to use market taker + if s.counter-s.maxCounterBuyCanceled <= 1 { + opt.LimitOrder = false + } opt.Price = source opt.Tags = []string{"long"} createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) @@ -738,28 +713,20 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { return } if shortCondition { - if err := s.GeneralOrderExecutor.CancelNoWait(ctx); err != nil { - log.WithError(err).Errorf("cannot cancel orders") - s.positionLock.Unlock() - return - } - source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last() * s.HighLowVarianceMultiplier)) if source.Compare(price) < 0 { source = price } - /*source = fixedpoint.NewFromFloat(s.ma.Last() + s.stdevHigh.Last()*s.HighLowVarianceMultiplier) - if source.Compare(price) < 0 { - source = price - } - sourcef = source.Float64()*/ log.Infof("source in short: %v", source) - s.positionLock.Unlock() opt := s.OpenPositionOptions opt.Short = true opt.Price = source + opt.LimitOrder = true + if s.counter-s.maxCounterSellCanceled <= 1 { + opt.LimitOrder = false + } opt.Tags = []string{"short"} createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) if err != nil { @@ -775,7 +742,6 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { } return } - s.positionLock.Unlock() } func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { @@ -842,13 +808,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } s.GeneralOrderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _profit, _netProfit fixedpoint.Value) { s.p.AddTrade(trade) - order, ok := s.GeneralOrderExecutor.TradeCollector().OrderStore().Get(trade.OrderID) - if !ok { - panic(fmt.Sprintf("cannot find order: %v", trade)) - } - tag := order.Tag - price := trade.Price.Float64() + delete(s.orderPendingCounter, trade.OrderID) if s.buyPrice > 0 { profit.Update(modify(price / s.buyPrice)) @@ -858,19 +819,18 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se cumProfit.Update(s.CalcAssetValue(trade.Price).Float64()) } s.positionLock.Lock() - defer s.positionLock.Unlock() if s.p.IsDust(trade.Price) { s.buyPrice = 0 s.sellPrice = 0 s.highestPrice = 0 s.lowestPrice = 0 } else if s.p.IsLong() { - s.buyPrice = s.p.ApproximateAverageCost.Float64() // trade.Price.Float64() + s.buyPrice = s.p.ApproximateAverageCost.Float64() s.sellPrice = 0 s.highestPrice = math.Max(s.buyPrice, s.highestPrice) s.lowestPrice = s.buyPrice } else if s.p.IsShort() { - s.sellPrice = s.p.ApproximateAverageCost.Float64() // trade.Price.Float64() + s.sellPrice = s.p.ApproximateAverageCost.Float64() s.buyPrice = 0 s.highestPrice = s.sellPrice if s.lowestPrice == 0 { @@ -879,7 +839,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.lowestPrice = math.Min(s.lowestPrice, s.sellPrice) } } - bbgo.Notify("tag: %s, sp: %.4f bp: %.4f hp: %.4f lp: %.4f, trade: %s, pos: %s", tag, s.sellPrice, s.buyPrice, s.highestPrice, s.lowestPrice, trade.String(), s.p.String()) + s.positionLock.Unlock() }) s.frameKLine = &types.KLine{} @@ -891,42 +851,43 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1d, s.startTime)) s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1w, s.startTime)) - // default value: use 1m kline - if !s.NoTrailingStopLoss && s.IsBackTesting() || s.TrailingStopLossType == "" { - s.TrailingStopLossType = "kline" - } - bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) { - canvas := s.DrawIndicators(s.frameKLine.StartTime) - var buffer bytes.Buffer - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render indicators in drift") - reply.Message(fmt.Sprintf("[error] cannot render indicators in drift: %v", err)) - return - } - bbgo.SendPhoto(&buffer) + go func() { + canvas := s.DrawIndicators(s.frameKLine.StartTime) + var buffer bytes.Buffer + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render indicators in drift") + reply.Message(fmt.Sprintf("[error] cannot render indicators in drift: %v", err)) + return + } + bbgo.SendPhoto(&buffer) + }() }) bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) { - canvas := s.DrawPNL(&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 drift: %v", err)) - return - } - bbgo.SendPhoto(&buffer) + go func() { + canvas := s.DrawPNL(&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 drift: %v", err)) + return + } + bbgo.SendPhoto(&buffer) + }() }) bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) { - canvas := s.DrawCumPNL(&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)) - return - } - bbgo.SendPhoto(&buffer) + go func() { + canvas := s.DrawCumPNL(&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)) + return + } + bbgo.SendPhoto(&buffer) + }() }) bbgo.RegisterCommand("/config", "Show latest config", func(reply interact.Reply) { @@ -965,7 +926,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } store.OnKLineClosed(func(kline types.KLine) { - s.counter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Milliseconds()) + s.counter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Milliseconds()) / s.MinInterval.Milliseconds() if kline.Interval == s.Interval { s.klineHandler(ctx, kline) } else if kline.Interval == s.MinInterval { diff --git a/pkg/strategy/elliottwave/strategy.go b/pkg/strategy/elliottwave/strategy.go index f135d4739..40703017a 100644 --- a/pkg/strategy/elliottwave/strategy.go +++ b/pkg/strategy/elliottwave/strategy.go @@ -104,7 +104,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { // this is not enough if we're subscribing 30m intervals using SerialMarketDataStore if !bbgo.IsBackTesting { session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{}) - session.Subscribe(types.AggTradeChannel, s.Symbol, types.SubscribeOptions{}) + session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{}) if s.MinInterval.Milliseconds() >= types.Interval1s.Milliseconds() && s.MinInterval.Milliseconds()%types.Interval1s.Milliseconds() == 0 { bbgo.KLinePreloadLimit = int64(((s.Interval.Milliseconds()/s.MinInterval.Milliseconds())*s.WindowSlow/1000 + 1) + 1000) session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{