From 5086af2886e5e880923523302832da4ecbede2be Mon Sep 17 00:00:00 2001 From: zenix Date: Wed, 28 Sep 2022 20:06:37 +0900 Subject: [PATCH] fix: reduce Quantity precheck, drift condition, ewo refactor --- config/driftBTC.yaml | 29 ++++---- config/elliottwave.yaml | 12 ++-- pkg/bbgo/order_executor_general.go | 13 +++- pkg/indicator/drift.go | 6 +- pkg/strategy/drift/strategy.go | 102 ++++++++++++--------------- pkg/strategy/elliottwave/strategy.go | 9 +-- pkg/strategy/irr/strategy.go | 4 +- 7 files changed, 89 insertions(+), 86 deletions(-) diff --git a/config/driftBTC.yaml b/config/driftBTC.yaml index f2baf4d68..1d33df2d0 100644 --- a/config/driftBTC.yaml +++ b/config/driftBTC.yaml @@ -27,29 +27,30 @@ exchangeStrategies: - on: binance drift: limitOrder: false + quantity: 0.001 canvasPath: "./output.png" symbol: BTCUSDT # kline interval for indicators - interval: 4m - window: 2 - stoploss: 0.13% - source: ohlc4 + interval: 1m + window: 4 + stoploss: 0.02% + source: hl2 predictOffset: 2 noTrailingStopLoss: false - trailingStopLossType: kline + trailingStopLossType: realtime # stddev on high/low-source - hlVarianceMultiplier: 0.22 + hlVarianceMultiplier: 0.2 hlRangeWindow: 4 - smootherWindow: 1 - fisherTransformWindow: 96 + smootherWindow: 31 + fisherTransformWindow: 80 window1m: 8 smootherWindow1m: 4 fisherTransformWindow1m: 320 atrWindow: 14 # orders not been traded will be canceled after `pendingMinutes` minutes - pendingMinutes: 10 + pendingMinutes: 3 noRebalance: true - trendWindow: 576 + trendWindow: 185 rebalanceFilter: 1.2 #driftFilterPos: 0.5 #driftFilterNeg: -0.5 @@ -60,12 +61,12 @@ exchangeStrategies: # 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.001, 0.0081, 0.022] - trailingActivationRatio: [0.0012, 0.0016, 0.01] + trailingActivationRatio: [0.0007, 0.0016, 0.008, 0.01] #trailingActivationRatio: [] #trailingCallbackRate: [] #trailingCallbackRate: [0.002, 0.01, 0.1] #trailingCallbackRate: [0.0004, 0.0009, 0.018] - trailingCallbackRate: [0.0003, 0.0006, 0.0019] + trailingCallbackRate: [0.0003, 0.0005, 0.0010, 0.0016] generateGraph: true graphPNLDeductFee: false @@ -128,7 +129,7 @@ sync: - BTCUSDT backtest: - startTime: "2022-09-01" + startTime: "2022-09-26" endTime: "2022-09-30" symbols: - BTCUSDT @@ -139,4 +140,4 @@ backtest: takerFeeRate: 0.000 balances: BTC: 0 - USDT: 1000 + USDT: 50 diff --git a/config/elliottwave.yaml b/config/elliottwave.yaml index 214bd6f22..d3d138f4c 100644 --- a/config/elliottwave.yaml +++ b/config/elliottwave.yaml @@ -24,12 +24,14 @@ exchangeStrategies: - on: binance elliottwave: symbol: BNBBUSD + limitOrder: true + quantity: 0.16 # kline interval for indicators - interval: 3m - stoploss: 0.2% + interval: 1m + stoploss: 0.01% windowATR: 14 windowQuick: 5 - windowSlow: 19 + windowSlow: 9 source: hl2 pendingMinutes: 10 useHeikinAshi: true @@ -49,7 +51,7 @@ exchangeStrategies: #trailingCallbackRate: [] #trailingCallbackRate: [0.002, 0.01, 0.1] #trailingCallbackRate: [0.0004, 0.0009, 0.018] - trailingCallbackRate: [0.0006, 0.0049, 0.006] + trailingCallbackRate: [0.0006, 0.0019, 0.006] #exits: # - roiStopLoss: @@ -119,4 +121,4 @@ backtest: takerFeeRate: 0.000 balances: BNB: 0 - BUSD: 20 + BUSD: 100 diff --git a/pkg/bbgo/order_executor_general.go b/pkg/bbgo/order_executor_general.go index 746c390f1..0078d57f2 100644 --- a/pkg/bbgo/order_executor_general.go +++ b/pkg/bbgo/order_executor_general.go @@ -230,7 +230,16 @@ func (e *GeneralOrderExecutor) reduceQuantityAndSubmitOrder(ctx context.Context, var err error for i := 0; i < submitOrderRetryLimit; i++ { q := submitOrder.Quantity.Mul(fixedpoint.One.Sub(quantityReduceDelta)) - log.Warnf("retrying order, adjusting order quantity: %f -> %f", submitOrder.Quantity.Float64(), q.Float64()) + if submitOrder.Side == types.SideTypeSell { + if baseBalance, ok := e.session.GetAccount().Balance(e.position.Market.BaseCurrency); ok { + q = fixedpoint.Min(q, baseBalance.Available) + } + } else { + if quoteBalance, ok := e.session.GetAccount().Balance(e.position.Market.QuoteCurrency); ok { + q = fixedpoint.Min(q, quoteBalance.Available.Div(price)) + } + } + log.Warnf("retrying order, adjusting order quantity: %v -> %v", submitOrder.Quantity, q) submitOrder.Quantity = q if e.position.Market.IsDustQuantity(submitOrder.Quantity, price) { @@ -260,7 +269,7 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos Tag: strings.Join(options.Tags, ","), } - baseBalance, _ := e.session.Account.Balance(e.position.Market.BaseCurrency) + baseBalance, _ := e.session.GetAccount().Balance(e.position.Market.BaseCurrency) // FIXME: fix the max quote borrowing checking // quoteBalance, _ := e.session.Account.Balance(e.position.Market.QuoteCurrency) diff --git a/pkg/indicator/drift.go b/pkg/indicator/drift.go index 81633f399..12006191f 100644 --- a/pkg/indicator/drift.go +++ b/pkg/indicator/drift.go @@ -14,9 +14,9 @@ import ( type Drift struct { types.SeriesBase types.IntervalWindow - chng *types.Queue - Values floats.Slice - MA types.UpdatableSeriesExtend + chng *types.Queue + Values floats.Slice + MA types.UpdatableSeriesExtend LastValue float64 UpdateCallbacks []func(value float64) diff --git a/pkg/strategy/drift/strategy.go b/pkg/strategy/drift/strategy.go index 616738e68..74b8edfc1 100644 --- a/pkg/strategy/drift/strategy.go +++ b/pkg/strategy/drift/strategy.go @@ -13,6 +13,7 @@ import ( "github.com/sirupsen/logrus" "github.com/wcharczuk/go-chart/v2" + "go.uber.org/multierr" "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/datatype/floats" @@ -37,6 +38,19 @@ func init() { bbgo.RegisterStrategy(ID, &Strategy{}) } +func filterErrors(errs []error) (es []error) { + for _, e := range errs { + if _, ok := e.(types.ZeroAssetError); ok { + continue + } + if bbgo.ErrExceededSubmitOrderRetryLimit == e { + continue + } + es = append(es, e) + } + return es +} + type Strategy struct { Symbol string `json:"symbol"` @@ -152,7 +166,6 @@ func (s *Strategy) CurrentPosition() *types.Position { func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { order := s.p.NewMarketCloseOrder(percentage) if order == nil { - s.positionLock.Unlock() return nil } order.Tag = "close" @@ -169,7 +182,6 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu order.Quantity = baseBalance } order.MarginSideEffect = types.SideEffectTypeAutoRepay - s.positionLock.Unlock() for { if s.Market.IsDustQuantity(order.Quantity, price) { return nil @@ -383,11 +395,11 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) { s.trailingCheck(pricef, "short")) exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= pricef || s.trailingCheck(pricef, "long")) + + s.positionLock.Unlock() if exitShortCondition || exitLongCondition { s.ClosePosition(ctx, fixedpoint.One) log.Infof("close position by orderbook changes") - } else { - s.positionLock.Unlock() } }) s.getLastPrice = func() (lastPrice fixedpoint.Value) { @@ -621,10 +633,9 @@ func (s *Strategy) klineHandler1m(ctx context.Context, kline types.KLine) { s.trailingCheck(highf, "short") /* || s.drift1m.Last() > 0*/) exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= lowf || s.trailingCheck(lowf, "long") /* || s.drift1m.Last() < 0*/) + s.positionLock.Unlock() if exitShortCondition || exitLongCondition { _ = s.ClosePosition(ctx, fixedpoint.One) - } else { - s.positionLock.Unlock() } } @@ -706,8 +717,8 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { return v <= 0 }, 50).Mean(50) - shortCondition := (drift[1] >= s.DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= s.DDriftFilterNeg || ddriftPred <= 0) || drift[1] < 0 && drift[0] < 0 - longCondition := (drift[1] <= s.DriftFilterPos || ddrift[1] <= 0) && (driftPred >= s.DDriftFilterPos || ddriftPred >= 0) || drift[1] > 0 && drift[0] > 0 + 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] { longCondition = false @@ -721,14 +732,16 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { s.trailingCheck(pricef, "long")) if exitShortCondition || exitLongCondition { + s.positionLock.Unlock() if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { log.WithError(err).Errorf("cannot cancel orders") - s.positionLock.Unlock() return } _ = s.ClosePosition(ctx, fixedpoint.One) - if longCondition || shortCondition { + if shortCondition || longCondition { s.positionLock.Lock() + } else { + return } } @@ -756,10 +769,11 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { opt.Tags = []string{"long"} createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) if err != nil { - if _, ok := err.(types.ZeroAssetError); ok { - return + errs := filterErrors(multierr.Errors(err)) + if len(errs) > 0 { + log.Errorf("%v", errs) + log.WithError(err).Errorf("cannot place buy order") } - log.WithError(err).Errorf("cannot place buy order") return } log.Infof("orders %v", createdOrders) @@ -774,6 +788,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { s.positionLock.Unlock() return } + /*source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last() * s.HighLowVarianceMultiplier)) if source.Compare(price) < 0 { source = price @@ -793,10 +808,10 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { opt.Tags = []string{"short"} createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) if err != nil { - if _, ok := err.(types.ZeroAssetError); ok { - return + errs := filterErrors(multierr.Errors(err)) + if len(errs) > 0 { + log.WithError(err).Errorf("cannot place sell order") } - log.WithError(err).Errorf("cannot place buy order") return } log.Infof("orders %v", createdOrders) @@ -889,46 +904,21 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } s.positionLock.Lock() defer s.positionLock.Unlock() - // tag == "" is for exits trades - if tag == "close" || tag == "" { - if s.p.IsDust(trade.Price) { - s.buyPrice = 0 - s.sellPrice = 0 - s.highestPrice = 0 - s.lowestPrice = 0 - } else if s.p.IsLong() { - s.sellPrice = 0 - s.lowestPrice = 0 - } else { - s.buyPrice = 0 - s.highestPrice = 0 - } - } else if tag == "long" { - 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 = trade.Price.Float64() - s.sellPrice = 0 - s.highestPrice = s.buyPrice - s.lowestPrice = 0 - } - } else if tag == "short" { - if s.p.IsDust(trade.Price) { - s.sellPrice = 0 - s.buyPrice = 0 - s.highestPrice = 0 - s.lowestPrice = 0 - } else if s.p.IsShort() { - s.sellPrice = trade.Price.Float64() - s.buyPrice = 0 - s.highestPrice = 0 - s.lowestPrice = s.sellPrice - } - } else { - panic("tag unknown") + 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.sellPrice = 0 + s.highestPrice = math.Max(s.buyPrice, s.highestPrice) + s.lowestPrice = 0 + } else if s.p.IsShort() { + s.sellPrice = s.p.ApproximateAverageCost.Float64() //trade.Price.Float64() + s.buyPrice = 0 + s.highestPrice = 0 + 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()) }) diff --git a/pkg/strategy/elliottwave/strategy.go b/pkg/strategy/elliottwave/strategy.go index bf9172e68..ce4f55d49 100644 --- a/pkg/strategy/elliottwave/strategy.go +++ b/pkg/strategy/elliottwave/strategy.go @@ -152,7 +152,8 @@ func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error { maSlow := &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.WindowSlow}} maQuick := &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.WindowQuick}} s.ewo = &ElliottWave{ - maSlow, maQuick, + maSlow: maSlow, + maQuick: maQuick, } s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.WindowATR}} klines, ok := store.KLinesOfInterval(s.Interval) @@ -371,10 +372,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.InitDrawCommands(store, &profit, &cumProfit) store.OnKLineClosed(func(kline types.KLine) { s.minutesCounter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Minutes()) - if kline.Interval == types.Interval1m { - s.klineHandler1m(ctx, kline) - } else if kline.Interval == s.Interval { + if kline.Interval == s.Interval { s.klineHandler(ctx, kline) + } else if kline.Interval == types.Interval1m { + s.klineHandler1m(ctx, kline) } }) diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 1b7afec95..71bf50c9e 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -213,9 +213,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // use kline direction to prevent reversing position too soon if diffQty.Sign() > 0 { // && kline.Direction() >= 0 - s.orderExecutor.OpenPosition(context.Background(), bbgo.OpenPositionOptions{Quantity: diffQty.Abs(), Long: true, MarketOrder: true}) + s.orderExecutor.OpenPosition(context.Background(), bbgo.OpenPositionOptions{Quantity: diffQty.Abs(), Long: true, LimitOrder: false}) } else if diffQty.Sign() < 0 { // && kline.Direction() <= 0 - s.orderExecutor.OpenPosition(context.Background(), bbgo.OpenPositionOptions{Quantity: diffQty.Abs(), Short: true, MarketOrder: true}) + s.orderExecutor.OpenPosition(context.Background(), bbgo.OpenPositionOptions{Quantity: diffQty.Abs(), Short: true, LimitOrder: false}) } }))