fix: reduce Quantity precheck, drift condition, ewo refactor

This commit is contained in:
zenix 2022-09-28 20:06:37 +09:00
parent 7b47a51fae
commit 5086af2886
7 changed files with 89 additions and 86 deletions

View File

@ -27,29 +27,30 @@ exchangeStrategies:
- on: binance - on: binance
drift: drift:
limitOrder: false limitOrder: false
quantity: 0.001
canvasPath: "./output.png" canvasPath: "./output.png"
symbol: BTCUSDT symbol: BTCUSDT
# kline interval for indicators # kline interval for indicators
interval: 4m interval: 1m
window: 2 window: 4
stoploss: 0.13% stoploss: 0.02%
source: ohlc4 source: hl2
predictOffset: 2 predictOffset: 2
noTrailingStopLoss: false noTrailingStopLoss: false
trailingStopLossType: kline trailingStopLossType: realtime
# stddev on high/low-source # stddev on high/low-source
hlVarianceMultiplier: 0.22 hlVarianceMultiplier: 0.2
hlRangeWindow: 4 hlRangeWindow: 4
smootherWindow: 1 smootherWindow: 31
fisherTransformWindow: 96 fisherTransformWindow: 80
window1m: 8 window1m: 8
smootherWindow1m: 4 smootherWindow1m: 4
fisherTransformWindow1m: 320 fisherTransformWindow1m: 320
atrWindow: 14 atrWindow: 14
# orders not been traded will be canceled after `pendingMinutes` minutes # orders not been traded will be canceled after `pendingMinutes` minutes
pendingMinutes: 10 pendingMinutes: 3
noRebalance: true noRebalance: true
trendWindow: 576 trendWindow: 185
rebalanceFilter: 1.2 rebalanceFilter: 1.2
#driftFilterPos: 0.5 #driftFilterPos: 0.5
#driftFilterNeg: -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 # 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.0012, 0.0016, 0.01] trailingActivationRatio: [0.0007, 0.0016, 0.008, 0.01]
#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.0003, 0.0006, 0.0019] trailingCallbackRate: [0.0003, 0.0005, 0.0010, 0.0016]
generateGraph: true generateGraph: true
graphPNLDeductFee: false graphPNLDeductFee: false
@ -128,7 +129,7 @@ sync:
- BTCUSDT - BTCUSDT
backtest: backtest:
startTime: "2022-09-01" startTime: "2022-09-26"
endTime: "2022-09-30" endTime: "2022-09-30"
symbols: symbols:
- BTCUSDT - BTCUSDT
@ -139,4 +140,4 @@ backtest:
takerFeeRate: 0.000 takerFeeRate: 0.000
balances: balances:
BTC: 0 BTC: 0
USDT: 1000 USDT: 50

View File

@ -24,12 +24,14 @@ exchangeStrategies:
- on: binance - on: binance
elliottwave: elliottwave:
symbol: BNBBUSD symbol: BNBBUSD
limitOrder: true
quantity: 0.16
# kline interval for indicators # kline interval for indicators
interval: 3m interval: 1m
stoploss: 0.2% stoploss: 0.01%
windowATR: 14 windowATR: 14
windowQuick: 5 windowQuick: 5
windowSlow: 19 windowSlow: 9
source: hl2 source: hl2
pendingMinutes: 10 pendingMinutes: 10
useHeikinAshi: true useHeikinAshi: true
@ -49,7 +51,7 @@ exchangeStrategies:
#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.0049, 0.006] trailingCallbackRate: [0.0006, 0.0019, 0.006]
#exits: #exits:
# - roiStopLoss: # - roiStopLoss:
@ -119,4 +121,4 @@ backtest:
takerFeeRate: 0.000 takerFeeRate: 0.000
balances: balances:
BNB: 0 BNB: 0
BUSD: 20 BUSD: 100

View File

@ -230,7 +230,16 @@ func (e *GeneralOrderExecutor) reduceQuantityAndSubmitOrder(ctx context.Context,
var err error var err error
for i := 0; i < submitOrderRetryLimit; i++ { for i := 0; i < submitOrderRetryLimit; i++ {
q := submitOrder.Quantity.Mul(fixedpoint.One.Sub(quantityReduceDelta)) 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 submitOrder.Quantity = q
if e.position.Market.IsDustQuantity(submitOrder.Quantity, price) { 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, ","), 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 // FIXME: fix the max quote borrowing checking
// quoteBalance, _ := e.session.Account.Balance(e.position.Market.QuoteCurrency) // quoteBalance, _ := e.session.Account.Balance(e.position.Market.QuoteCurrency)

View File

@ -13,6 +13,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"go.uber.org/multierr"
"github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/datatype/floats"
@ -37,6 +38,19 @@ func init() {
bbgo.RegisterStrategy(ID, &Strategy{}) 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 { type Strategy struct {
Symbol string `json:"symbol"` 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 { func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error {
order := s.p.NewMarketCloseOrder(percentage) order := s.p.NewMarketCloseOrder(percentage)
if order == nil { if order == nil {
s.positionLock.Unlock()
return nil return nil
} }
order.Tag = "close" order.Tag = "close"
@ -169,7 +182,6 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
order.Quantity = baseBalance order.Quantity = baseBalance
} }
order.MarginSideEffect = types.SideEffectTypeAutoRepay order.MarginSideEffect = types.SideEffectTypeAutoRepay
s.positionLock.Unlock()
for { for {
if s.Market.IsDustQuantity(order.Quantity, price) { if s.Market.IsDustQuantity(order.Quantity, price) {
return nil return nil
@ -383,11 +395,11 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
s.trailingCheck(pricef, "short")) s.trailingCheck(pricef, "short"))
exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= pricef || exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= pricef ||
s.trailingCheck(pricef, "long")) s.trailingCheck(pricef, "long"))
s.positionLock.Unlock()
if exitShortCondition || exitLongCondition { if exitShortCondition || exitLongCondition {
s.ClosePosition(ctx, fixedpoint.One) s.ClosePosition(ctx, fixedpoint.One)
log.Infof("close position by orderbook changes") log.Infof("close position by orderbook changes")
} else {
s.positionLock.Unlock()
} }
}) })
s.getLastPrice = func() (lastPrice fixedpoint.Value) { 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*/) s.trailingCheck(highf, "short") /* || s.drift1m.Last() > 0*/)
exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= lowf || exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= lowf ||
s.trailingCheck(lowf, "long") /* || s.drift1m.Last() < 0*/) s.trailingCheck(lowf, "long") /* || s.drift1m.Last() < 0*/)
s.positionLock.Unlock()
if exitShortCondition || exitLongCondition { if exitShortCondition || exitLongCondition {
_ = s.ClosePosition(ctx, fixedpoint.One) _ = 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 return v <= 0
}, 50).Mean(50) }, 50).Mean(50)
shortCondition := (drift[1] >= s.DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= s.DDriftFilterNeg || 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] <= s.DriftFilterPos || ddrift[1] <= 0) && (driftPred >= s.DDriftFilterPos || ddriftPred >= 0) || drift[1] > 0 && drift[0] > 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 shortCondition && longCondition {
if drift[1] > drift[0] { if drift[1] > drift[0] {
longCondition = false longCondition = false
@ -721,14 +732,16 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.trailingCheck(pricef, "long")) s.trailingCheck(pricef, "long"))
if exitShortCondition || exitLongCondition { if exitShortCondition || exitLongCondition {
s.positionLock.Unlock()
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
log.WithError(err).Errorf("cannot cancel orders") log.WithError(err).Errorf("cannot cancel orders")
s.positionLock.Unlock()
return return
} }
_ = s.ClosePosition(ctx, fixedpoint.One) _ = s.ClosePosition(ctx, fixedpoint.One)
if longCondition || shortCondition { if shortCondition || longCondition {
s.positionLock.Lock() s.positionLock.Lock()
} else {
return
} }
} }
@ -756,10 +769,11 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
opt.Tags = []string{"long"} opt.Tags = []string{"long"}
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
if err != nil { if err != nil {
if _, ok := err.(types.ZeroAssetError); ok { errs := filterErrors(multierr.Errors(err))
return 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 return
} }
log.Infof("orders %v", createdOrders) log.Infof("orders %v", createdOrders)
@ -774,6 +788,7 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
s.positionLock.Unlock() s.positionLock.Unlock()
return return
} }
/*source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last() * s.HighLowVarianceMultiplier)) /*source = source.Add(fixedpoint.NewFromFloat(s.stdevHigh.Last() * s.HighLowVarianceMultiplier))
if source.Compare(price) < 0 { if source.Compare(price) < 0 {
source = price source = price
@ -793,10 +808,10 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
opt.Tags = []string{"short"} opt.Tags = []string{"short"}
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
if err != nil { if err != nil {
if _, ok := err.(types.ZeroAssetError); ok { errs := filterErrors(multierr.Errors(err))
return if len(errs) > 0 {
log.WithError(err).Errorf("cannot place sell order")
} }
log.WithError(err).Errorf("cannot place buy order")
return return
} }
log.Infof("orders %v", createdOrders) log.Infof("orders %v", createdOrders)
@ -889,46 +904,21 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
s.positionLock.Lock() s.positionLock.Lock()
defer s.positionLock.Unlock() defer s.positionLock.Unlock()
// tag == "" is for exits trades
if tag == "close" || tag == "" {
if s.p.IsDust(trade.Price) { if s.p.IsDust(trade.Price) {
s.buyPrice = 0 s.buyPrice = 0
s.sellPrice = 0 s.sellPrice = 0
s.highestPrice = 0 s.highestPrice = 0
s.lowestPrice = 0 s.lowestPrice = 0
} else if s.p.IsLong() { } else if s.p.IsLong() {
s.buyPrice = s.p.ApproximateAverageCost.Float64() //trade.Price.Float64()
s.sellPrice = 0 s.sellPrice = 0
s.lowestPrice = 0 s.highestPrice = math.Max(s.buyPrice, s.highestPrice)
} 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 s.lowestPrice = 0
} else if s.p.IsShort() { } else if s.p.IsShort() {
s.sellPrice = trade.Price.Float64() s.sellPrice = s.p.ApproximateAverageCost.Float64() //trade.Price.Float64()
s.buyPrice = 0 s.buyPrice = 0
s.highestPrice = 0 s.highestPrice = 0
s.lowestPrice = s.sellPrice s.lowestPrice = math.Min(s.lowestPrice, s.sellPrice)
}
} else {
panic("tag unknown")
} }
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()) 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}} maSlow := &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.WindowSlow}}
maQuick := &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.WindowQuick}} maQuick := &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.WindowQuick}}
s.ewo = &ElliottWave{ s.ewo = &ElliottWave{
maSlow, maQuick, maSlow: maSlow,
maQuick: maQuick,
} }
s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.WindowATR}} s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.WindowATR}}
klines, ok := store.KLinesOfInterval(s.Interval) 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) s.InitDrawCommands(store, &profit, &cumProfit)
store.OnKLineClosed(func(kline types.KLine) { store.OnKLineClosed(func(kline types.KLine) {
s.minutesCounter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Minutes()) s.minutesCounter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Minutes())
if kline.Interval == types.Interval1m { if kline.Interval == s.Interval {
s.klineHandler1m(ctx, kline)
} else if kline.Interval == s.Interval {
s.klineHandler(ctx, kline) 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 // use kline direction to prevent reversing position too soon
if diffQty.Sign() > 0 { // && kline.Direction() >= 0 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 } 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})
} }
})) }))