Merge pull request #969 from zenixls2/fix/precheck_reduceQuantity

fix: reduce Quantity precheck, drift condition, ewo refactor
This commit is contained in:
Yo-An Lin 2022-09-28 19:59:36 +08:00 committed by GitHub
commit ffdf2a69a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 89 additions and 86 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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())
})

View File

@ -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)
}
})

View File

@ -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})
}
}))