fix: rate settings in telegram, make elliottwave draw async

This commit is contained in:
zenix 2022-10-19 17:59:00 +09:00
parent 3d672ea518
commit 17825fbde1
7 changed files with 127 additions and 142 deletions

View File

@ -24,17 +24,17 @@ exchangeStrategies:
- on: binance - on: binance
elliottwave: elliottwave:
minInterval: 1s minInterval: 1s
symbol: BNBBUSD symbol: BTCUSDT
limitOrder: true limitOrder: true
quantity: 0.16 #quantity: 0.16
# kline interval for indicators # kline interval for indicators
interval: 1m interval: 1s
stoploss: 0.01% stoploss: 0.01%
windowATR: 14 windowATR: 14
windowQuick: 5 windowQuick: 4
windowSlow: 9 windowSlow: 155
source: hl2 source: hl2
pendingMinutes: 10 pendingMinInterval: 5
useHeikinAshi: true useHeikinAshi: true
drawGraph: true drawGraph: true
@ -47,12 +47,12 @@ exchangeStrategies:
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop # when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
#trailingActivationRatio: [0.01, 0.016, 0.05] #trailingActivationRatio: [0.01, 0.016, 0.05]
#trailingActivationRatio: [0.001, 0.0081, 0.022] #trailingActivationRatio: [0.001, 0.0081, 0.022]
trailingActivationRatio: [0.0017, 0.01, 0.015] #trailingActivationRatio: [0.0017, 0.01, 0.015]
#trailingActivationRatio: [] trailingActivationRatio: []
#trailingCallbackRate: [] trailingCallbackRate: []
#trailingCallbackRate: [0.002, 0.01, 0.1] #trailingCallbackRate: [0.002, 0.01, 0.1]
#trailingCallbackRate: [0.0004, 0.0009, 0.018] #trailingCallbackRate: [0.0004, 0.0009, 0.018]
trailingCallbackRate: [0.0006, 0.0019, 0.006] #trailingCallbackRate: [0.0006, 0.0019, 0.006]
#exits: #exits:
# - roiStopLoss: # - roiStopLoss:
@ -108,13 +108,13 @@ sync:
sessions: sessions:
- binance - binance
symbols: symbols:
- BNBBUSD - BTCUSDT
backtest: backtest:
startTime: "2022-09-01" startTime: "2022-10-15"
endTime: "2022-09-30" endTime: "2022-10-19"
symbols: symbols:
- BNBBUSD - BTCUSDT
sessions: [binance] sessions: [binance]
syncSecKLines: true syncSecKLines: true
accounts: accounts:
@ -122,5 +122,5 @@ backtest:
makerFeeRate: 0.000 makerFeeRate: 0.000
takerFeeRate: 0.000 takerFeeRate: 0.000
balances: balances:
BNB: 0 BTC: 0
BUSD: 100 USDT: 100

View File

@ -46,7 +46,7 @@ func (store *SerialMarketDataStore) Subscribe(interval types.Interval) {
func (store *SerialMarketDataStore) BindStream(ctx context.Context, stream types.Stream) { func (store *SerialMarketDataStore) BindStream(ctx context.Context, stream types.Stream) {
if store.UseAggTrade { if store.UseAggTrade {
if IsBackTesting { if IsBackTesting {
log.Errorf("Right now in backtesting, aggTrade event is not yet supported. Use OnKLineClosed instead.") log.Errorf("right now in backtesting, aggTrade event is not yet supported. Use OnKLineClosed instead.")
stream.OnKLineClosed(store.handleKLineClosed) stream.OnKLineClosed(store.handleKLineClosed)
return return
} }
@ -85,6 +85,10 @@ func (store *SerialMarketDataStore) handleMarketTrade(trade types.Trade) {
func (store *SerialMarketDataStore) tickerProcessor(ctx context.Context) { func (store *SerialMarketDataStore) tickerProcessor(ctx context.Context) {
duration := store.MinInterval.Duration() duration := store.MinInterval.Duration()
relativeTime := time.Now().UnixNano() % int64(duration)
waitTime := int64(duration) - relativeTime
ch := time.After(time.Duration(waitTime))
<-ch
intervalCloseTicker := time.NewTicker(duration) intervalCloseTicker := time.NewTicker(duration)
defer intervalCloseTicker.Stop() defer intervalCloseTicker.Stop()

View File

@ -166,52 +166,6 @@ func (trader *Trader) SetRiskControls(riskControls *RiskControls) {
trader.riskControls = riskControls trader.riskControls = riskControls
} }
func (trader *Trader) Subscribe() {
// pre-subscribe the data
for sessionName, strategies := range trader.exchangeStrategies {
session := trader.environment.sessions[sessionName]
for _, strategy := range strategies {
if defaulter, ok := strategy.(StrategyDefaulter); ok {
if err := defaulter.Defaults(); err != nil {
panic(err)
}
}
if initializer, ok := strategy.(StrategyInitializer); ok {
if err := initializer.Initialize(); err != nil {
panic(err)
}
}
if subscriber, ok := strategy.(ExchangeSessionSubscriber); ok {
subscriber.Subscribe(session)
} else {
log.Errorf("strategy %s does not implement ExchangeSessionSubscriber", strategy.ID())
}
}
}
for _, strategy := range trader.crossExchangeStrategies {
if defaulter, ok := strategy.(StrategyDefaulter); ok {
if err := defaulter.Defaults(); err != nil {
panic(err)
}
}
if initializer, ok := strategy.(StrategyInitializer); ok {
if err := initializer.Initialize(); err != nil {
panic(err)
}
}
if subscriber, ok := strategy.(CrossExchangeSessionSubscriber); ok {
subscriber.CrossSubscribe(trader.environment.sessions)
} else {
log.Errorf("strategy %s does not implement CrossExchangeSessionSubscriber", strategy.ID())
}
}
}
func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy SingleExchangeStrategy, session *ExchangeSession, orderExecutor OrderExecutor) error { func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy SingleExchangeStrategy, session *ExchangeSession, orderExecutor OrderExecutor) error {
if v, ok := strategy.(StrategyValidator); ok { if v, ok := strategy.(StrategyValidator); ok {
if err := v.Validate(); err != nil { if err := v.Validate(); err != nil {
@ -262,7 +216,7 @@ func (trader *Trader) RunAllSingleExchangeStrategy(ctx context.Context) error {
return nil return nil
} }
func (trader *Trader) injectFields(ctx context.Context) error { func (trader *Trader) injectFieldsAndSubscribe(ctx context.Context) error {
// load and run Session strategies // load and run Session strategies
for sessionName, strategies := range trader.exchangeStrategies { for sessionName, strategies := range trader.exchangeStrategies {
var session = trader.environment.sessions[sessionName] var session = trader.environment.sessions[sessionName]
@ -350,6 +304,24 @@ func (trader *Trader) injectFields(ctx context.Context) error {
if err := trader.injectCommonServices(strategy); err != nil { if err := trader.injectCommonServices(strategy); err != nil {
return err return err
} }
if defaulter, ok := strategy.(StrategyDefaulter); ok {
if err := defaulter.Defaults(); err != nil {
return err
}
}
if initializer, ok := strategy.(StrategyInitializer); ok {
if err := initializer.Initialize(); err != nil {
return err
}
}
if subscriber, ok := strategy.(CrossExchangeSessionSubscriber); ok {
subscriber.CrossSubscribe(trader.environment.sessions)
} else {
log.Errorf("strategy %s does not implement CrossExchangeSessionSubscriber", strategy.ID())
}
} }
return nil return nil
@ -361,7 +333,7 @@ func (trader *Trader) Run(ctx context.Context) error {
// trader.environment.Connect will call interact.Start // trader.environment.Connect will call interact.Start
interact.AddCustomInteraction(NewCoreInteraction(trader.environment, trader)) interact.AddCustomInteraction(NewCoreInteraction(trader.environment, trader))
if err := trader.injectFields(ctx); err != nil { if err := trader.injectFieldsAndSubscribe(ctx); err != nil {
return err return err
} }

View File

@ -15,7 +15,7 @@ import (
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
var apiLimiter = rate.NewLimiter(rate.Every(1*time.Second), 1) var apiLimiter = rate.NewLimiter(rate.Every(50*time.Millisecond), 20)
var log = logrus.WithField("service", "telegram") var log = logrus.WithField("service", "telegram")

View File

@ -98,7 +98,7 @@ type Strategy struct {
SmootherWindow int `json:"smootherWindow"` SmootherWindow int `json:"smootherWindow"`
FisherTransformWindow int `json:"fisherTransformWindow"` FisherTransformWindow int `json:"fisherTransformWindow"`
ATRWindow int `json:"atrWindow"` ATRWindow int `json:"atrWindow"`
PendingMinutes int `json:"pendingMinutes" modifiable:"true"` // if order not be traded for pendingMinutes of time, cancel it. 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 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 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 RebalanceFilter float64 `json:"rebalanceFilter" modifiable:"true"` // beta filter on the Linear Regression of trendLine
@ -276,7 +276,7 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, e
continue continue
} }
log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.counter) log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.counter)
if s.counter-s.orderPendingCounter[order.OrderID] > s.PendingMinutes { if s.counter-s.orderPendingCounter[order.OrderID] > s.PendingMinInterval {
toCancel = true toCancel = true
} else if order.Side == types.SideTypeBuy { } else if order.Side == types.SideTypeBuy {
// 75% of the probability // 75% of the probability

View File

@ -13,6 +13,7 @@ import (
func (s *Strategy) InitDrawCommands(store *bbgo.SerialMarketDataStore, profit, cumProfit types.Series) { func (s *Strategy) InitDrawCommands(store *bbgo.SerialMarketDataStore, profit, cumProfit types.Series) {
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) { bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
go func() {
canvas := s.DrawIndicators(store) canvas := s.DrawIndicators(store)
if canvas == nil { if canvas == nil {
reply.Message("cannot render indicators") reply.Message("cannot render indicators")
@ -25,35 +26,36 @@ func (s *Strategy) InitDrawCommands(store *bbgo.SerialMarketDataStore, profit, c
return return
} }
bbgo.SendPhoto(&buffer) bbgo.SendPhoto(&buffer)
}()
}) })
bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) { bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) {
go func() {
canvas := s.DrawPNL(profit) canvas := s.DrawPNL(profit)
var buffer bytes.Buffer var buffer bytes.Buffer
if err := canvas.Render(chart.PNG, &buffer); err != nil { if err := canvas.Render(chart.PNG, &buffer); err != nil {
log.WithError(err).Errorf("cannot render pnl in drift") log.WithError(err).Errorf("cannot render pnl in ewo")
reply.Message(fmt.Sprintf("[error] cannot render pnl in ewo: %v", err)) reply.Message(fmt.Sprintf("[error] cannot render pnl in ewo: %v", err))
return return
} }
bbgo.SendPhoto(&buffer) bbgo.SendPhoto(&buffer)
}()
}) })
bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) { bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) {
go func() {
canvas := s.DrawCumPNL(cumProfit) canvas := s.DrawCumPNL(cumProfit)
var buffer bytes.Buffer var buffer bytes.Buffer
if err := canvas.Render(chart.PNG, &buffer); err != nil { if err := canvas.Render(chart.PNG, &buffer); err != nil {
log.WithError(err).Errorf("cannot render cumpnl in drift") log.WithError(err).Errorf("cannot render cumpnl in ewo")
reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err)) reply.Message(fmt.Sprintf("[error] canot render cumpnl in ewo: %v", err))
return return
} }
bbgo.SendPhoto(&buffer) bbgo.SendPhoto(&buffer)
}()
}) })
} }
func (s *Strategy) DrawIndicators(store *bbgo.SerialMarketDataStore) *types.Canvas { func (s *Strategy) DrawIndicators(store *bbgo.SerialMarketDataStore) *types.Canvas {
klines, ok := store.KLinesOfInterval(types.Interval1m) time := types.Time(s.startTime)
if !ok {
return nil
}
time := (*klines)[len(*klines)-1].StartTime
canvas := types.NewCanvas(s.InstanceID(), s.Interval) canvas := types.NewCanvas(s.InstanceID(), s.Interval)
Length := s.priceLines.Length() Length := s.priceLines.Length()
if Length > 300 { if Length > 300 {
@ -109,10 +111,10 @@ func (s *Strategy) Draw(store *bbgo.SerialMarketDataStore, profit, cumProfit typ
log.WithError(err).Errorf("cannot create on path " + s.GraphIndicatorPath) log.WithError(err).Errorf("cannot create on path " + s.GraphIndicatorPath)
return return
} }
defer f.Close()
if err = canvas.Render(chart.PNG, f); err != nil { if err = canvas.Render(chart.PNG, f); err != nil {
log.WithError(err).Errorf("cannot render elliottwave") log.WithError(err).Errorf("cannot render elliottwave")
} }
f.Close()
canvas = s.DrawPNL(profit) canvas = s.DrawPNL(profit)
f, err = os.Create(s.GraphPNLPath) f, err = os.Create(s.GraphPNLPath)
@ -120,19 +122,19 @@ func (s *Strategy) Draw(store *bbgo.SerialMarketDataStore, profit, cumProfit typ
log.WithError(err).Errorf("cannot create on path " + s.GraphPNLPath) log.WithError(err).Errorf("cannot create on path " + s.GraphPNLPath)
return return
} }
defer f.Close()
if err = canvas.Render(chart.PNG, f); err != nil { if err = canvas.Render(chart.PNG, f); err != nil {
log.WithError(err).Errorf("cannot render pnl") log.WithError(err).Errorf("cannot render pnl")
return return
} }
f.Close()
canvas = s.DrawCumPNL(cumProfit) canvas = s.DrawCumPNL(cumProfit)
f, err = os.Create(s.GraphCumPNLPath) f, err = os.Create(s.GraphCumPNLPath)
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot create on path " + s.GraphCumPNLPath) log.WithError(err).Errorf("cannot create on path " + s.GraphCumPNLPath)
return return
} }
defer f.Close()
if err = canvas.Render(chart.PNG, f); err != nil { if err = canvas.Render(chart.PNG, f); err != nil {
log.WithError(err).Errorf("cannot render cumpnl") log.WithError(err).Errorf("cannot render cumpnl")
} }
f.Close()
} }

View File

@ -49,7 +49,7 @@ type Strategy struct {
WindowATR int `json:"windowATR"` WindowATR int `json:"windowATR"`
WindowQuick int `json:"windowQuick"` WindowQuick int `json:"windowQuick"`
WindowSlow int `json:"windowSlow"` WindowSlow int `json:"windowSlow"`
PendingMinutes int `json:"pendingMinutes" modifiable:"true"` PendingMinInterval int `json:"pendingMinInterval" modifiable:"true"`
UseHeikinAshi bool `json:"useHeikinAshi"` UseHeikinAshi bool `json:"useHeikinAshi"`
// whether to draw graph or not by the end of backtest // whether to draw graph or not by the end of backtest
@ -196,7 +196,7 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef float64) int {
} }
log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.counter) log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.counter)
toCancel := false toCancel := false
if s.counter-s.orderPendingCounter[order.OrderID] >= s.PendingMinutes { if s.counter-s.orderPendingCounter[order.OrderID] >= s.PendingMinInterval {
toCancel = true toCancel = true
} else if order.Side == types.SideTypeBuy { } else if order.Side == types.SideTypeBuy {
if order.Price.Float64()+s.atr.Last()*2 <= pricef { if order.Price.Float64()+s.atr.Last()*2 <= pricef {
@ -211,7 +211,7 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef float64) int {
panic("not supported side for the order") panic("not supported side for the order")
} }
if toCancel { if toCancel {
err := s.GeneralOrderExecutor.GracefulCancel(ctx, order) err := s.GeneralOrderExecutor.CancelNoWait(ctx, order)
if err == nil { if err == nil {
delete(s.orderPendingCounter, order.OrderID) delete(s.orderPendingCounter, order.OrderID)
} else { } else {
@ -235,6 +235,9 @@ func (s *Strategy) trailingCheck(price float64, direction string) bool {
s.lowestPrice = price s.lowestPrice = price
} }
isShort := direction == "short" isShort := direction == "short"
if isShort && s.sellPrice == 0 || !isShort && s.buyPrice == 0 {
return false
}
for i := len(s.TrailingCallbackRate) - 1; i >= 0; i-- { for i := len(s.TrailingCallbackRate) - 1; i >= 0; i-- {
trailingCallbackRate := s.TrailingCallbackRate[i] trailingCallbackRate := s.TrailingCallbackRate[i]
trailingActivationRatio := s.TrailingActivationRatio[i] trailingActivationRatio := s.TrailingActivationRatio[i]
@ -244,7 +247,7 @@ func (s *Strategy) trailingCheck(price float64, direction string) bool {
} }
} else { } else {
if (s.highestPrice-s.buyPrice)/s.buyPrice > trailingActivationRatio { if (s.highestPrice-s.buyPrice)/s.buyPrice > trailingActivationRatio {
return (s.highestPrice-price)/price > trailingCallbackRate return (s.highestPrice-price)/s.buyPrice > trailingCallbackRate
} }
} }
} }
@ -351,15 +354,19 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.highestPrice = 0 s.highestPrice = 0
s.lowestPrice = 0 s.lowestPrice = 0
} else if s.Position.IsLong() { } else if s.Position.IsLong() {
s.buyPrice = price s.buyPrice = s.Position.ApproximateAverageCost.Float64()
s.sellPrice = 0 s.sellPrice = 0
s.highestPrice = s.buyPrice s.highestPrice = math.Max(s.buyPrice, s.highestPrice)
s.lowestPrice = 0 s.lowestPrice = 0
} else { } else {
s.sellPrice = price s.sellPrice = s.Position.ApproximateAverageCost.Float64()
s.buyPrice = 0 s.buyPrice = 0
s.highestPrice = 0 s.highestPrice = 0
if s.lowestPrice == 0 {
s.lowestPrice = s.sellPrice s.lowestPrice = s.sellPrice
} else {
s.lowestPrice = math.Min(s.lowestPrice, s.sellPrice)
}
} }
}) })
s.initTickerFunctions() s.initTickerFunctions()
@ -477,6 +484,8 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
bull := kline.Close.Compare(kline.Open) > 0 bull := kline.Close.Compare(kline.Open) > 0
balances := s.GeneralOrderExecutor.Session().GetAccount().Balances() balances := s.GeneralOrderExecutor.Session().GetAccount().Balances()
startTime := kline.StartTime.Time()
if startTime.Round(time.Second) == startTime.Round(time.Minute) {
bbgo.Notify("source: %.4f, price: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f", sourcef, pricef, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice) bbgo.Notify("source: %.4f, price: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f", sourcef, pricef, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice)
bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s", bbgo.Notify("balances: [Total] %v %s [Base] %s(%v %s) [Quote] %s",
s.CalcAssetValue(price), s.CalcAssetValue(price),
@ -486,6 +495,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.Market.QuoteCurrency, s.Market.QuoteCurrency,
balances[s.Market.QuoteCurrency].String(), balances[s.Market.QuoteCurrency].String(),
) )
}
shortCondition := ewo[0] < ewo[1] && ewo[1] >= ewo[2] && (ewo[1] <= ewo[2] || ewo[2] >= ewo[3]) || s.sellPrice == 0 && ewo[0] < ewo[1] && ewo[1] < ewo[2] shortCondition := ewo[0] < ewo[1] && ewo[1] >= ewo[2] && (ewo[1] <= ewo[2] || ewo[2] >= ewo[3]) || s.sellPrice == 0 && ewo[0] < ewo[1] && ewo[1] < ewo[2]
longCondition := ewo[0] > ewo[1] && ewo[1] <= ewo[2] && (ewo[1] >= ewo[2] || ewo[2] <= ewo[3]) || s.buyPrice == 0 && ewo[0] > ewo[1] && ewo[1] > ewo[2] longCondition := ewo[0] > ewo[1] && ewo[1] <= ewo[2] && (ewo[1] >= ewo[2] || ewo[2] <= ewo[3]) || s.buyPrice == 0 && ewo[0] > ewo[1] && ewo[1] > ewo[2]
@ -493,18 +503,19 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
exitShortCondition := s.sellPrice > 0 && !shortCondition && s.sellPrice*(1.+stoploss) <= highf || s.sellPrice+atr <= highf || s.trailingCheck(highf, "short") exitShortCondition := s.sellPrice > 0 && !shortCondition && s.sellPrice*(1.+stoploss) <= highf || s.sellPrice+atr <= highf || s.trailingCheck(highf, "short")
exitLongCondition := s.buyPrice > 0 && !longCondition && s.buyPrice*(1.-stoploss) >= lowf || s.buyPrice-atr >= lowf || s.trailingCheck(lowf, "long") exitLongCondition := s.buyPrice > 0 && !longCondition && s.buyPrice*(1.-stoploss) >= lowf || s.buyPrice-atr >= lowf || s.trailingCheck(lowf, "long")
if exitShortCondition || exitLongCondition { if exitShortCondition || exitLongCondition || (longCondition && bull) || (shortCondition && !bull) {
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { if hold := s.smartCancel(ctx, pricef); hold > 0 {
log.WithError(err).Errorf("cannot cancel orders")
return return
} }
} else {
s.smartCancel(ctx, pricef)
return
}
if exitShortCondition || exitLongCondition {
s.ClosePosition(ctx, fixedpoint.One) s.ClosePosition(ctx, fixedpoint.One)
} }
if longCondition && bull { if longCondition && bull {
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
log.WithError(err).Errorf("cannot cancel orders")
return
}
if source.Compare(price) > 0 { if source.Compare(price) > 0 {
source = price source = price
} }
@ -527,10 +538,6 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
return return
} }
if shortCondition && !bull { if shortCondition && !bull {
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
log.WithError(err).Errorf("cannot cancel orders")
return
}
if source.Compare(price) < 0 { if source.Compare(price) < 0 {
source = price source = price
} }