From fdbcaef2cac41a5ed053bddb7102503f2a63c7ef Mon Sep 17 00:00:00 2001 From: zenix Date: Thu, 22 Sep 2022 20:26:18 +0900 Subject: [PATCH] fix: use ZeroAssetError, refactor --- pkg/bbgo/order_executor_general.go | 62 +++++++++++------------------- pkg/bbgo/risk.go | 7 +++- pkg/dynamic/field.go | 3 +- pkg/dynamic/field_test.go | 2 - pkg/strategy/drift/strategy.go | 12 ++++-- pkg/types/error.go | 10 +++++ pkg/types/filter.go | 51 ++++++++++++++++++++++++ pkg/types/indicator.go | 45 ---------------------- 8 files changed, 99 insertions(+), 93 deletions(-) create mode 100644 pkg/types/filter.go diff --git a/pkg/bbgo/order_executor_general.go b/pkg/bbgo/order_executor_general.go index 645c74f12..ca94919c7 100644 --- a/pkg/bbgo/order_executor_general.go +++ b/pkg/bbgo/order_executor_general.go @@ -219,7 +219,23 @@ type OpenPositionOptions struct { Tags []string `json:"-" yaml:"-"` } -var Delta fixedpoint.Value = fixedpoint.NewFromFloat(0.005) +// Delta used to modify the order to submit, especially for the market order +var QuantityReduceDelta fixedpoint.Value = fixedpoint.NewFromFloat(0.005) + +func (e *GeneralOrderExecutor) reduceQuantityAndSubmitOrder(ctx context.Context, price fixedpoint.Value, submitOrder types.SubmitOrder) (types.OrderSlice, error) { + for { + createdOrder, err2 := e.SubmitOrders(ctx, submitOrder) + if err2 != nil { + submitOrder.Quantity = submitOrder.Quantity.Mul(fixedpoint.One.Sub(QuantityReduceDelta)) + if e.position.Market.IsDustQuantity(submitOrder.Quantity, price) { + return nil, err2 + } + continue + } + log.Infof("created order: %+v", createdOrder) + return createdOrder, nil + } +} func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPositionOptions) (types.OrderSlice, error) { price := options.Price @@ -252,24 +268,16 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos quantity := options.Quantity - market, ok := e.session.Market(e.symbol) - if !ok { - return nil, errors.New("cannot find market with symbol " + e.symbol) - } - if options.Long { if quantity.IsZero() { quoteQuantity, err := CalculateQuoteQuantity(ctx, e.session, e.position.QuoteCurrency, options.Leverage) - if quoteQuantity.IsZero() { - log.Warnf("dust quantity: %v", quantity) - return nil, nil - } if err != nil { return nil, err } + quantity = quoteQuantity.Div(price) } - if market.IsDustQuantity(quantity, price) { + if e.position.Market.IsDustQuantity(quantity, price) { log.Warnf("dust quantity: %v", quantity) return nil, nil } @@ -284,31 +292,16 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos submitOrder.Quantity = quantity Notify("Opening %s long position with quantity %v at price %v", e.position.Symbol, quantity, price) - for { - createdOrder, err2 := e.SubmitOrders(ctx, submitOrder) - if err2 != nil { - submitOrder.Quantity = submitOrder.Quantity.Mul(fixedpoint.One.Sub(Delta)) - if market.IsDustQuantity(submitOrder.Quantity, price) { - return nil, err2 - } - continue - } - log.Infof("created order: %+v", createdOrder) - return createdOrder, nil - } + return e.reduceQuantityAndSubmitOrder(ctx, price, submitOrder) } else if options.Short { if quantity.IsZero() { var err error quantity, err = CalculateBaseQuantity(e.session, e.position.Market, price, quantity, options.Leverage) - if quantity.IsZero() { - log.Warnf("dust quantity: %v", quantity) - return nil, nil - } if err != nil { return nil, err } } - if market.IsDustQuantity(quantity, price) { + if e.position.Market.IsDustQuantity(quantity, price) { log.Warnf("dust quantity: %v", quantity) return nil, nil } @@ -323,18 +316,7 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos submitOrder.Quantity = quantity Notify("Opening %s short position with quantity %v at price %v", e.position.Symbol, quantity, price) - for { - createdOrder, err2 := e.SubmitOrders(ctx, submitOrder) - if err2 != nil { - submitOrder.Quantity = submitOrder.Quantity.Mul(fixedpoint.One.Sub(Delta)) - if market.IsDustQuantity(submitOrder.Quantity, price) { - return nil, err2 - } - continue - } - log.Infof("created order: %+v", createdOrder) - return createdOrder, nil - } + return e.reduceQuantityAndSubmitOrder(ctx, price, submitOrder) } return nil, errors.New("options Long or Short must be set") diff --git a/pkg/bbgo/risk.go b/pkg/bbgo/risk.go index 633ae42e2..8648424d9 100644 --- a/pkg/bbgo/risk.go +++ b/pkg/bbgo/risk.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/c9s/bbgo/pkg/fixedpoint" @@ -243,7 +244,8 @@ func CalculateBaseQuantity(session *ExchangeSession, market types.Market, price, } } - return quantity, fmt.Errorf("quantity is zero, can not submit sell order, please check your quantity settings, your account balances: %+v", balances) + return quantity, types.NewZeroAssetError( + fmt.Errorf("quantity is zero, can not submit sell order, please check your quantity settings, your account balances: %+v", balances)) } usdBalances, restBalances := usdFiatBalances(balances) @@ -329,7 +331,8 @@ func CalculateBaseQuantity(session *ExchangeSession, market types.Market, price, return maxPositionQuantity, nil } - return quantity, fmt.Errorf("quantity is zero, can not submit sell order, please check your settings") + return quantity, types.NewZeroAssetError( + errors.New("quantity is zero, can not submit sell order, please check your settings")) } func CalculateQuoteQuantity(ctx context.Context, session *ExchangeSession, quoteCurrency string, leverage fixedpoint.Value) (fixedpoint.Value, error) { diff --git a/pkg/dynamic/field.go b/pkg/dynamic/field.go index 9f4709141..c004458de 100644 --- a/pkg/dynamic/field.go +++ b/pkg/dynamic/field.go @@ -36,7 +36,8 @@ func GetModifiableFields(val reflect.Value, callback func(tagName, name string)) if !val.IsValid() { return } - for i := 0; i < val.Type().NumField(); i++ { + num := val.Type().NumField() + for i := 0; i < num; i++ { t := val.Type().Field(i) if !t.IsExported() { continue diff --git a/pkg/dynamic/field_test.go b/pkg/dynamic/field_test.go index 862f37380..a57ae9228 100644 --- a/pkg/dynamic/field_test.go +++ b/pkg/dynamic/field_test.go @@ -2,7 +2,6 @@ package dynamic import ( "encoding/json" - "fmt" "reflect" "testing" @@ -35,7 +34,6 @@ func TestGetModifiableFields(t *testing.T) { assert.NotEqual(t, name, "Field2") assert.NotEqual(t, tagName, "field3") assert.NotEqual(t, name, "Field3") - fmt.Println(tagName, name) }) } diff --git a/pkg/strategy/drift/strategy.go b/pkg/strategy/drift/strategy.go index c64c0a8aa..fb49f689b 100644 --- a/pkg/strategy/drift/strategy.go +++ b/pkg/strategy/drift/strategy.go @@ -384,7 +384,7 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) { s.trailingCheck(pricef, "long")) if exitShortCondition || exitLongCondition { if s.ClosePosition(ctx, fixedpoint.One) { - log.Infof("Close position by orderbook changes") + log.Infof("close position by orderbook changes") } } else { s.positionLock.Unlock() @@ -752,11 +752,14 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { opt.Price = source opt.Tags = []string{"long"} createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) - log.Infof("orders %v", createdOrders) if err != nil { + if _, ok := err.(types.ZeroAssetError); ok { + return + } log.WithError(err).Errorf("cannot place buy order") return } + log.Infof("orders %v", createdOrders) if createdOrders != nil { s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter } @@ -786,11 +789,14 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) { opt.Price = source opt.Tags = []string{"long"} createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) - log.Infof("orders %v", createdOrders) if err != nil { + if _, ok := err.(types.ZeroAssetError); ok { + return + } log.WithError(err).Errorf("cannot place buy order") return } + log.Infof("orders %v", createdOrders) if createdOrders != nil { s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter } diff --git a/pkg/types/error.go b/pkg/types/error.go index 48ce67fac..5d58b57b8 100644 --- a/pkg/types/error.go +++ b/pkg/types/error.go @@ -21,3 +21,13 @@ func NewOrderError(e error, o Order) error { order: o, } } + +type ZeroAssetError struct { + error +} + +func NewZeroAssetError(e error) ZeroAssetError { + return ZeroAssetError{ + error: e, + } +} diff --git a/pkg/types/filter.go b/pkg/types/filter.go new file mode 100644 index 000000000..58e0a966e --- /dev/null +++ b/pkg/types/filter.go @@ -0,0 +1,51 @@ +package types + +type FilterResult struct { + a Series + b func(int, float64) bool + length int + c []int +} + +func (f *FilterResult) Last() float64 { + return f.Index(0) +} + +func (f *FilterResult) Index(j int) float64 { + if j >= f.length { + return 0 + } + if len(f.c) > j { + return f.a.Index(f.c[j]) + } + l := f.a.Length() + k := len(f.c) + i := 0 + if k > 0 { + i = f.c[k-1] + 1 + } + for ; i < l; i++ { + tmp := f.a.Index(i) + if f.b(i, tmp) { + f.c = append(f.c, i) + if j == k { + return tmp + } + k++ + } + } + return 0 +} + +func (f *FilterResult) Length() int { + return f.length +} + +// Filter function filters Series by using a boolean function. +// When the boolean function returns true, the Series value at index i will be included in the returned Series. +// The returned Series will find at most `length` latest matching elements from the input Series. +// Query index larger or equal than length from the returned Series will return 0 instead. +// Notice that any Update on the input Series will make the previously returned Series outdated. +func Filter(a Series, b func(i int, value float64) bool, length int) SeriesExtend { + return NewSeries(&FilterResult{a, b, length, nil}) +} diff --git a/pkg/types/indicator.go b/pkg/types/indicator.go index d71c88247..aed7330ac 100644 --- a/pkg/types/indicator.go +++ b/pkg/types/indicator.go @@ -998,51 +998,6 @@ func Rolling(a Series, window int) *RollingResult { return &RollingResult{a, window} } -type FilterResult struct { - a Series - b func(int, float64) bool - length int - c []int -} - -func (f *FilterResult) Last() float64 { - return f.Index(0) -} - -func (f *FilterResult) Index(j int) float64 { - if j >= f.length { - return 0 - } - if len(f.c) > j { - return f.a.Index(f.c[j]) - } - l := f.a.Length() - k := len(f.c) - i := 0 - if k > 0 { - i = f.c[k-1] + 1 - } - for ; i < l; i++ { - tmp := f.a.Index(i) - if f.b(i, tmp) { - f.c = append(f.c, i) - if j == k { - return tmp - } - k++ - } - } - return 0 -} - -func (f *FilterResult) Length() int { - return f.length -} - -func Filter(a Series, b func(i int, value float64) bool, length int) SeriesExtend { - return NewSeries(&FilterResult{a, b, length, nil}) -} - type SigmoidResult struct { a Series }