diff --git a/config/ewo_dgtrd.yaml b/config/ewo_dgtrd.yaml new file mode 100644 index 000000000..f8c655de4 --- /dev/null +++ b/config/ewo_dgtrd.yaml @@ -0,0 +1,48 @@ +--- +sessions: + binance: + exchange: binance + futures: true + envVarPrefix: binance + +exchangeStrategies: + +- on: binance + ewo_dgtrd: + symbol: NEARUSDT + interval: 15m + useEma: false + useSma: false + sigWin: 3 + stoploss: 2% + callback: 1% + useHeikinAshi: true + #stops: + #- trailingStop: + # callbackRate: 5.1% + # closePosition: 20% + # minProfit: 1% + # interval: 1m + # virtual: true + +sync: + userDataStream: + trades: true + filledOrders: true + since: 2020-12-01 + sessions: + - binance + symbols: + - NEARUSDT + +backtest: + startTime: "2022-03-03" + endTime: "2022-04-14" + symbols: + - NEARUSDT + sessions: [binance] + account: + binance: + balances: + NEAR: 0 + USDT: 10000 diff --git a/pkg/cmd/builtin.go b/pkg/cmd/builtin.go index d97bd1b17..670cbc963 100644 --- a/pkg/cmd/builtin.go +++ b/pkg/cmd/builtin.go @@ -23,4 +23,5 @@ import ( _ "github.com/c9s/bbgo/pkg/strategy/xmaker" _ "github.com/c9s/bbgo/pkg/strategy/xnav" _ "github.com/c9s/bbgo/pkg/strategy/xpuremaker" + _ "github.com/c9s/bbgo/pkg/strategy/ewoDgtrd" ) diff --git a/pkg/strategy/ewoDgtrd/strategy.go b/pkg/strategy/ewoDgtrd/strategy.go new file mode 100644 index 000000000..a4fe469e2 --- /dev/null +++ b/pkg/strategy/ewoDgtrd/strategy.go @@ -0,0 +1,737 @@ +package ewoDgtrd + +import ( + "context" + "fmt" + "math" + "sync" + + "github.com/sirupsen/logrus" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" +) + +const ID = "ewo_dgtrd" + +var log = logrus.WithField("strategy", ID) +var modifier = fixedpoint.NewFromFloat(0.995) + +func init() { + bbgo.RegisterStrategy(ID, &Strategy{}) +} + +type Strategy struct { + Market types.Market + Session *bbgo.ExchangeSession + UseHeikinAshi bool `json:"useHeikinAshi"` // use heikinashi kline + Stoploss fixedpoint.Value `json:"stoploss"` + Callback fixedpoint.Value `json:"callback"` + Symbol string `json:"symbol"` + Interval types.Interval `json:"interval"` + UseEma bool `json:"useEma"` // use exponential ma or not + UseSma bool `json:"useSma"` // if UseEma == false, use simple ma or not + SignalWindow int `json:"sigWin"` // signal window + + *bbgo.Graceful + bbgo.SmartStops + tradeCollector *bbgo.TradeCollector + ma5 types.Series + ma34 types.Series + ewo types.Series + ewoSignal types.Series + heikinAshi *HeikinAshi + peakPrice fixedpoint.Value + bottomPrice fixedpoint.Value +} + +func (s *Strategy) ID() string { + return ID +} + +func (s *Strategy) Initialize() error { + return s.SmartStops.InitializeStopControllers(s.Symbol) +} + +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { + log.Infof("subscribe %s", s.Symbol) + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval.String()}) + s.SmartStops.Subscribe(session) +} + +type UpdatableSeries interface { + types.Series + Update(value float64) +} + +type EVWMA struct { + PV UpdatableSeries + V UpdatableSeries +} + +func (inc *EVWMA) Last() float64 { + return inc.PV.Last() / inc.V.Last() +} + +func (inc *EVWMA) Index(i int) float64 { + if i >= inc.PV.Length() { + return 0 + } + vi := inc.V.Index(i) + if vi == 0 { + return 0 + } + return inc.PV.Index(i) / vi +} + +func (inc *EVWMA) Length() int { + pvl := inc.PV.Length() + vl := inc.V.Length() + if pvl < vl { + return pvl + } + return vl +} + +func (inc *EVWMA) Update(kline types.KLine) { + inc.PV.Update(kline.Close.Mul(kline.Volume).Float64()) + inc.V.Update(kline.Volume.Float64()) +} + +func (inc *EVWMA) UpdateVal(price float64, vol float64) { + inc.PV.Update(price * vol) + inc.V.Update(vol) +} + +type Queue struct { + arr []float64 + size int +} + +func NewQueue(size int) *Queue { + return &Queue{ + arr: make([]float64, 0, size), + size: size, + } +} + +func (inc *Queue) Last() float64 { + if len(inc.arr) == 0 { + return 0 + } + return inc.arr[len(inc.arr)-1] +} + +func (inc *Queue) Index(i int) float64 { + if len(inc.arr)-i-1 < 0 { + return 0 + } + return inc.arr[len(inc.arr)-i-1] +} + +func (inc *Queue) Length() int { + return len(inc.arr) +} + +func (inc *Queue) Update(v float64) { + inc.arr = append(inc.arr, v) + if len(inc.arr) > inc.size { + inc.arr = inc.arr[len(inc.arr)-inc.size:] + } +} + +type HeikinAshi struct { + Close *Queue + Open *Queue + High *Queue + Low *Queue + Volume *Queue +} + +func NewHeikinAshi(size int) *HeikinAshi { + return &HeikinAshi{ + Close: NewQueue(size), + Open: NewQueue(size), + High: NewQueue(size), + Low: NewQueue(size), + Volume: NewQueue(size), + } +} + +func (s *HeikinAshi) Print() string { + return fmt.Sprintf("Heikin c: %.3f, o: %.3f, h: %.3f, l: %.3f, v: %.3f", + s.Close.Last(), + s.Open.Last(), + s.High.Last(), + s.Low.Last(), + s.Volume.Last()) +} + +func (inc *HeikinAshi) Update(kline types.KLine) { + open := kline.Open.Float64() + cloze := kline.Close.Float64() + high := kline.High.Float64() + low := kline.Low.Float64() + newClose := (open + high + low + cloze) / 4. + newOpen := (inc.Open.Last() + inc.Close.Last()) / 2. + inc.Close.Update(newClose) + inc.Open.Update(newOpen) + inc.High.Update(math.Max(math.Max(high, newOpen), newClose)) + inc.Low.Update(math.Min(math.Min(low, newOpen), newClose)) + inc.Volume.Update(kline.Volume.Float64()) +} + +func (s *Strategy) SetupIndicators() { + store, ok := s.Session.MarketDataStore(s.Symbol) + if !ok { + log.Errorf("cannot get marketdatastore of %s", s.Symbol) + return + } + + if s.UseHeikinAshi { + s.heikinAshi = NewHeikinAshi(50) + store.OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow) { + if s.Interval != interval { + return + } + if s.heikinAshi.Close.Length() == 0 { + for _, kline := range window { + s.heikinAshi.Update(kline) + } + } else { + s.heikinAshi.Update(window[len(window)-1]) + } + }) + if s.UseEma { + ema5 := &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}} + ema34 := &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}} + store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) { + if s.Interval != interval { + return + } + if ema5.Length() == 0 { + closes := types.ToReverseArray(s.heikinAshi.Close) + for _, cloze := range closes { + ema5.Update(cloze) + ema34.Update(cloze) + } + } else { + cloze := s.heikinAshi.Close.Last() + ema5.Update(cloze) + ema34.Update(cloze) + } + }) + s.ma5 = ema5 + s.ma34 = ema34 + } else if s.UseSma { + sma5 := &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}} + sma34 := &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}} + store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) { + if s.Interval != interval { + return + } + if sma5.Length() == 0 { + closes := types.ToReverseArray(s.heikinAshi.Close) + for _, cloze := range closes { + sma5.Update(cloze) + sma34.Update(cloze) + } + } else { + cloze := s.heikinAshi.Close.Last() + sma5.Update(cloze) + sma34.Update(cloze) + } + }) + s.ma5 = sma5 + s.ma34 = sma34 + } else { + evwma5 := &EVWMA{ + PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}, + V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}, + } + evwma34 := &EVWMA{ + PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}, + V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}, + } + store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) { + if s.Interval != interval { + return + } + if evwma5.PV.Length() == 0 { + for i := s.heikinAshi.Close.Length() - 1; i >= 0; i-- { + price := s.heikinAshi.Close.Index(i) + vol := s.heikinAshi.Volume.Index(i) + evwma5.UpdateVal(price, vol) + evwma34.UpdateVal(price, vol) + } + } else { + price := s.heikinAshi.Close.Last() + vol := s.heikinAshi.Volume.Last() + evwma5.UpdateVal(price, vol) + evwma34.UpdateVal(price, vol) + } + }) + s.ma5 = evwma5 + s.ma34 = evwma34 + } + } else { + indicatorSet, ok := s.Session.StandardIndicatorSet(s.Symbol) + if !ok { + log.Errorf("cannot get indicator set of %s", s.Symbol) + return + } + if s.UseEma { + s.ma5 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 5}) + s.ma34 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 34}) + } else if s.UseSma { + s.ma5 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 5}) + s.ma34 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 34}) + } else { + evwma5 := &EVWMA{ + PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}, + V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}, + } + evwma34 := &EVWMA{ + PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}, + V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}, + } + store.OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow) { + if s.Interval != interval { + return + } + if evwma5.PV.Length() == 0 { + for _, kline := range window { + evwma5.Update(kline) + evwma34.Update(kline) + } + } else { + evwma5.Update(window[len(window)-1]) + evwma34.Update(window[len(window)-1]) + } + }) + s.ma5 = evwma5 + s.ma34 = evwma34 + } + } + + s.ewo = types.Mul(types.Minus(types.Div(s.ma5, s.ma34), 1.0), 100.) + if s.UseEma { + sig := &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}} + store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) { + if interval != s.Interval { + return + } + + if sig.Length() == 0 { + // lazy init + ewoVals := types.ToReverseArray(s.ewo) + for _, ewoValue := range ewoVals { + sig.Update(ewoValue) + } + } else { + sig.Update(s.ewo.Last()) + } + }) + s.ewoSignal = sig + } else if s.UseSma { + sig := &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}} + store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) { + if interval != s.Interval { + return + } + + if sig.Length() == 0 { + // lazy init + ewoVals := types.ToReverseArray(s.ewo) + for _, ewoValue := range ewoVals { + sig.Update(ewoValue) + } + } else { + sig.Update(s.ewo.Last()) + } + }) + s.ewoSignal = sig + } else { + sig := &EVWMA{ + PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}, + V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}, + } + store.OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow) { + if interval != s.Interval { + return + } + var vol float64 + if sig.Length() == 0 { + // lazy init + ewoVals := types.ToReverseArray(s.ewo) + for i, ewoValue := range ewoVals { + if s.UseHeikinAshi { + vol = s.heikinAshi.Volume.Index(len(ewoVals) - 1 - i) + } else { + vol = window[len(ewoVals)-1-i].Volume.Float64() + } + sig.PV.Update(ewoValue * vol) + sig.V.Update(vol) + } + } else { + if s.UseHeikinAshi { + vol = s.heikinAshi.Volume.Last() + } else { + vol = window[len(window)-1].Volume.Float64() + } + sig.PV.Update(s.ewo.Last() * vol) + sig.V.Update(vol) + } + }) + s.ewoSignal = sig + } +} + +func (s *Strategy) validateOrder(order *types.SubmitOrder) bool { + if order.Type == types.OrderTypeMarket && order.TimeInForce != "" { + return false + } + if order.Side == types.SideTypeSell { + baseBalance, ok := s.Session.Account.Balance(s.Market.BaseCurrency) + if !ok { + return false + } + if order.Quantity.Compare(baseBalance.Available) > 0 { + return false + } + price := order.Price + if price.IsZero() { + price, ok = s.Session.LastPrice(s.Symbol) + if !ok { + return false + } + } + orderAmount := order.Quantity.Mul(price) + if order.Quantity.Sign() <= 0 || + order.Quantity.Compare(s.Market.MinQuantity) < 0 || + orderAmount.Compare(s.Market.MinNotional) < 0 { + return false + } + return true + } else if order.Side == types.SideTypeBuy { + quoteBalance, ok := s.Session.Account.Balance(s.Market.QuoteCurrency) + if !ok { + return false + } + price := order.Price + if price.IsZero() { + price, ok = s.Session.LastPrice(s.Symbol) + if !ok { + return false + } + } + totalQuantity := quoteBalance.Available.Div(price) + if order.Quantity.Compare(totalQuantity) > 0 { + return false + } + orderAmount := order.Quantity.Mul(price) + if order.Quantity.Sign() <= 0 || + orderAmount.Compare(s.Market.MinNotional) < 0 || + order.Quantity.Compare(s.Market.MinQuantity) < 0 { + return false + } + return true + } + return false + +} + +func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + buyPrice := fixedpoint.Zero + sellPrice := fixedpoint.Zero + s.peakPrice = fixedpoint.Zero + s.bottomPrice = fixedpoint.Zero + + orderbook, ok := session.OrderStore(s.Symbol) + if !ok { + log.Errorf("cannot get orderbook of %s", s.Symbol) + return nil + } + position, ok := session.Position(s.Symbol) + if !ok { + log.Errorf("cannot get position of %s", s.Symbol) + return nil + } + s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, position, orderbook) + s.tradeCollector.OnTrade(func(trade types.Trade, profit, netprofit fixedpoint.Value) { + if !profit.IsZero() { + log.Warnf("generate profit: %v, netprofit: %v, trade: %v", profit, netprofit, trade) + } + if trade.Side == types.SideTypeBuy { + buyPrice = trade.Price + s.peakPrice = buyPrice.Mul(fixedpoint.One.Add(s.Callback)) + } else if trade.Side == types.SideTypeSell { + sellPrice = trade.Price + s.bottomPrice = sellPrice.Mul(fixedpoint.One.Sub(s.Callback)) + } + }) + + s.tradeCollector.OnPositionUpdate(func(position *types.Position) { + log.Infof("position changed: %s", position) + }) + s.tradeCollector.BindStream(session.UserDataStream) + + s.SmartStops.RunStopControllers(ctx, session, s.tradeCollector) + + s.SetupIndicators() + + session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { + if kline.Symbol != s.Symbol { + return + } + + lastPrice, ok := session.LastPrice(s.Symbol) + if !ok { + log.Errorf("cannot get last price") + return + } + balances := session.Account.Balances() + baseBalance := balances[s.Market.BaseCurrency].Available + quoteBalance := balances[s.Market.QuoteCurrency].Available + /*if buyPrice.IsZero() { + if !baseBalance.IsZero() { + buyPrice = lastPrice + } + } + if sellPrice.IsZero() { + if !quoteBalance.IsZero() { + sellPrice = lastPrice + } + }*/ + + // cancel non-traded orders + var toCancel []types.Order + var toRepost []types.SubmitOrder + for _, order := range orderbook.Orders() { + if order.Status == types.OrderStatusNew || order.Status == types.OrderStatusPartiallyFilled { + toCancel = append(toCancel, order) + } + } + if len(toCancel) > 0 { + if err := orderExecutor.CancelOrders(ctx, toCancel...); err != nil { + log.WithError(err).Errorf("cancel order error") + } + + s.tradeCollector.Process() + } + + // well, only track prices on 1m + if kline.Interval == types.Interval1m { + for _, order := range toCancel { + if order.Side == types.SideTypeBuy && order.Price.Compare(kline.Low) < 0 { + newPrice := kline.Low + order.Quantity = order.Quantity.Mul(order.Price).Div(newPrice) + order.Price = newPrice + toRepost = append(toRepost, order.SubmitOrder) + } else if order.Side == types.SideTypeSell && order.Price.Compare(kline.High) > 0 { + newPrice := kline.High + order.Price = newPrice + toRepost = append(toRepost, order.SubmitOrder) + } + } + + if len(toRepost) > 0 { + createdOrders, err := orderExecutor.SubmitOrders(ctx, toRepost...) + if err != nil { + log.WithError(err).Errorf("cannot place order") + return + } + log.Infof("repost order %v", createdOrders) + s.tradeCollector.Process() + } + sellall := false + buyall := false + if !s.peakPrice.IsZero() { + change := s.peakPrice.Sub(lastPrice).Div(s.peakPrice) + if change.Compare(s.Callback) > 0 { + if !baseBalance.IsZero() { + sellall = true + } + s.peakPrice = fixedpoint.Zero + } else { + s.peakPrice = kline.High + } + } + if !s.bottomPrice.IsZero() { + change := lastPrice.Sub(s.bottomPrice).Div(s.bottomPrice) + if change.Compare(s.Callback) > 0 { + if !quoteBalance.IsZero() { + buyall = true + } + s.bottomPrice = fixedpoint.Zero + } else { + s.bottomPrice = kline.Low + } + } + + if !buyPrice.IsZero() && + buyPrice.Sub(lastPrice).Div(buyPrice).Compare(s.Stoploss) > 0 { // stoploss 2% + sellall = true + } + if !sellPrice.IsZero() && + lastPrice.Sub(sellPrice).Div(sellPrice).Compare(s.Stoploss) > 0 { // stoploss 2% + buyall = true + } + if sellall { + balances := session.Account.Balances() + baseBalance := balances[s.Market.BaseCurrency].Available.Mul(modifier) + order := types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeSell, + Type: types.OrderTypeMarket, + Market: s.Market, + Quantity: baseBalance, + } + if s.validateOrder(&order) { + log.Infof("stoploss short at %v, avg %v, timestamp: %s", lastPrice, buyPrice, kline.StartTime) + createdOrders, err := orderExecutor.SubmitOrders(ctx, order) + if err != nil { + log.WithError(err).Errorf("cannot place order") + return + } + log.Infof("stoploss sell order %v", createdOrders) + s.tradeCollector.Process() + } + } + + if buyall { + quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency) + if !ok { + return + } + quantityAmount := quoteBalance.Available.Mul(modifier) + totalQuantity := quantityAmount.Div(lastPrice) + order := types.SubmitOrder{ + Symbol: kline.Symbol, + Side: types.SideTypeBuy, + Type: types.OrderTypeMarket, + Quantity: totalQuantity, + Market: s.Market, + } + if s.validateOrder(&order) { + log.Infof("stoploss long at %v, avg %v, timestamp: %s", lastPrice, sellPrice, kline.StartTime) + + createdOrders, err := orderExecutor.SubmitOrders(ctx, order) + if err != nil { + log.WithError(err).Errorf("cannot place order") + return + } + log.Infof("stoploss bought order %v", createdOrders) + s.tradeCollector.Process() + } + } + } + + if kline.Interval != s.Interval { + return + } + + // To get the threshold for ewo + mean := types.Mean(types.Abs(s.ewo), 5) + + longSignal := types.CrossOver(s.ewo, s.ewoSignal) + shortSignal := types.CrossUnder(s.ewo, s.ewoSignal) + // get trend flags + var bull, breakThrough, breakDown bool + if s.UseHeikinAshi { + // heikinashi itself contains the concept of trend, so no need breakthrough/down + bull = s.heikinAshi.Close.Last() > s.heikinAshi.Open.Last() + breakThrough = true + breakDown = true + } else { + bull = types.Predict(s.ma34, 5, 2) > s.ma34.Last() + breakThrough = kline.Low.Float64() > s.ma5.Last() + breakDown = kline.High.Float64() < s.ma5.Last() + } + // kline breakthrough ma5, ma50 trend up, and ewo > threshold + IsBull := bull && breakThrough && s.ewo.Last() >= mean + // kline downthrough ma5, ma50 trend down, and ewo < threshold + IsBear := !bull && breakDown && s.ewo.Last() <= -mean + + var orders []types.SubmitOrder + var price fixedpoint.Value + + if longSignal.Index(1) && !shortSignal.Last() && IsBull { + if s.UseHeikinAshi { + price = fixedpoint.NewFromFloat(s.heikinAshi.Close.Last()) + } else { + price = kline.Low + } + quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency) + if !ok { + return + } + quantityAmount := quoteBalance.Available.Mul(modifier) + totalQuantity := quantityAmount.Div(price) + order := types.SubmitOrder{ + Symbol: kline.Symbol, + Side: types.SideTypeBuy, + Type: types.OrderTypeLimit, + Price: price, + Quantity: totalQuantity, + Market: s.Market, + TimeInForce: types.TimeInForceGTC, + } + if s.validateOrder(&order) { + // strong long + log.Warnf("long at %v, timestamp: %s", price, kline.StartTime) + + orders = append(orders, order) + } + } else if shortSignal.Index(1) && !longSignal.Last() && IsBear { + if s.UseHeikinAshi { + price = fixedpoint.NewFromFloat(s.heikinAshi.Close.Last()) + } else { + price = kline.High + } + balances := session.Account.Balances() + baseBalance := balances[s.Market.BaseCurrency].Available.Mul(modifier) + order := types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeSell, + Type: types.OrderTypeLimit, + Market: s.Market, + Quantity: baseBalance, + Price: price, + TimeInForce: types.TimeInForceGTC, + } + if s.validateOrder(&order) { + log.Warnf("short at %v, timestamp: %s", price, kline.StartTime) + orders = append(orders, order) + } + } + if len(orders) > 0 { + createdOrders, err := orderExecutor.SubmitOrders(ctx, orders...) + if err != nil { + log.WithError(err).Errorf("cannot place order") + return + } + log.Infof("post order %v", createdOrders) + s.tradeCollector.Process() + } + }) + s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) { + defer wg.Done() + log.Infof("canceling active orders...") + + var toCancel []types.Order + for _, order := range orderbook.Orders() { + if order.Status == types.OrderStatusNew || order.Status == types.OrderStatusPartiallyFilled { + toCancel = append(toCancel, order) + } + } + + if err := orderExecutor.CancelOrders(ctx, toCancel...); err != nil { + log.WithError(err).Errorf("cancel order error") + } + s.tradeCollector.Process() + }) + return nil +}