fix: use ZeroAssetError, refactor

This commit is contained in:
zenix 2022-09-22 20:26:18 +09:00
parent ac2f7decdf
commit fdbcaef2ca
8 changed files with 99 additions and 93 deletions

View File

@ -219,7 +219,23 @@ type OpenPositionOptions struct {
Tags []string `json:"-" yaml:"-"` 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) { func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPositionOptions) (types.OrderSlice, error) {
price := options.Price price := options.Price
@ -252,24 +268,16 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
quantity := options.Quantity 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 options.Long {
if quantity.IsZero() { if quantity.IsZero() {
quoteQuantity, err := CalculateQuoteQuantity(ctx, e.session, e.position.QuoteCurrency, options.Leverage) 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 { if err != nil {
return nil, err return nil, err
} }
quantity = quoteQuantity.Div(price) quantity = quoteQuantity.Div(price)
} }
if market.IsDustQuantity(quantity, price) { if e.position.Market.IsDustQuantity(quantity, price) {
log.Warnf("dust quantity: %v", quantity) log.Warnf("dust quantity: %v", quantity)
return nil, nil return nil, nil
} }
@ -284,31 +292,16 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
submitOrder.Quantity = quantity submitOrder.Quantity = quantity
Notify("Opening %s long position with quantity %v at price %v", e.position.Symbol, quantity, price) Notify("Opening %s long position with quantity %v at price %v", e.position.Symbol, quantity, price)
for { return e.reduceQuantityAndSubmitOrder(ctx, price, submitOrder)
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
}
} else if options.Short { } else if options.Short {
if quantity.IsZero() { if quantity.IsZero() {
var err error var err error
quantity, err = CalculateBaseQuantity(e.session, e.position.Market, price, quantity, options.Leverage) 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 { if err != nil {
return nil, err return nil, err
} }
} }
if market.IsDustQuantity(quantity, price) { if e.position.Market.IsDustQuantity(quantity, price) {
log.Warnf("dust quantity: %v", quantity) log.Warnf("dust quantity: %v", quantity)
return nil, nil return nil, nil
} }
@ -323,18 +316,7 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
submitOrder.Quantity = quantity submitOrder.Quantity = quantity
Notify("Opening %s short position with quantity %v at price %v", e.position.Symbol, quantity, price) Notify("Opening %s short position with quantity %v at price %v", e.position.Symbol, quantity, price)
for { return e.reduceQuantityAndSubmitOrder(ctx, price, submitOrder)
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 nil, errors.New("options Long or Short must be set") return nil, errors.New("options Long or Short must be set")

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/fixedpoint" "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) usdBalances, restBalances := usdFiatBalances(balances)
@ -329,7 +331,8 @@ func CalculateBaseQuantity(session *ExchangeSession, market types.Market, price,
return maxPositionQuantity, nil 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) { func CalculateQuoteQuantity(ctx context.Context, session *ExchangeSession, quoteCurrency string, leverage fixedpoint.Value) (fixedpoint.Value, error) {

View File

@ -36,7 +36,8 @@ func GetModifiableFields(val reflect.Value, callback func(tagName, name string))
if !val.IsValid() { if !val.IsValid() {
return return
} }
for i := 0; i < val.Type().NumField(); i++ { num := val.Type().NumField()
for i := 0; i < num; i++ {
t := val.Type().Field(i) t := val.Type().Field(i)
if !t.IsExported() { if !t.IsExported() {
continue continue

View File

@ -2,7 +2,6 @@ package dynamic
import ( import (
"encoding/json" "encoding/json"
"fmt"
"reflect" "reflect"
"testing" "testing"
@ -35,7 +34,6 @@ func TestGetModifiableFields(t *testing.T) {
assert.NotEqual(t, name, "Field2") assert.NotEqual(t, name, "Field2")
assert.NotEqual(t, tagName, "field3") assert.NotEqual(t, tagName, "field3")
assert.NotEqual(t, name, "Field3") assert.NotEqual(t, name, "Field3")
fmt.Println(tagName, name)
}) })
} }

View File

@ -384,7 +384,7 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
s.trailingCheck(pricef, "long")) s.trailingCheck(pricef, "long"))
if exitShortCondition || exitLongCondition { if exitShortCondition || exitLongCondition {
if s.ClosePosition(ctx, fixedpoint.One) { if s.ClosePosition(ctx, fixedpoint.One) {
log.Infof("Close position by orderbook changes") log.Infof("close position by orderbook changes")
} }
} else { } else {
s.positionLock.Unlock() s.positionLock.Unlock()
@ -752,11 +752,14 @@ func (s *Strategy) klineHandler(ctx context.Context, kline types.KLine) {
opt.Price = source opt.Price = source
opt.Tags = []string{"long"} opt.Tags = []string{"long"}
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
log.Infof("orders %v", createdOrders)
if err != nil { if err != nil {
if _, ok := err.(types.ZeroAssetError); ok {
return
}
log.WithError(err).Errorf("cannot place buy order") log.WithError(err).Errorf("cannot place buy order")
return return
} }
log.Infof("orders %v", createdOrders)
if createdOrders != nil { if createdOrders != nil {
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter 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.Price = source
opt.Tags = []string{"long"} opt.Tags = []string{"long"}
createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt) createdOrders, err := s.GeneralOrderExecutor.OpenPosition(ctx, opt)
log.Infof("orders %v", createdOrders)
if err != nil { if err != nil {
if _, ok := err.(types.ZeroAssetError); ok {
return
}
log.WithError(err).Errorf("cannot place buy order") log.WithError(err).Errorf("cannot place buy order")
return return
} }
log.Infof("orders %v", createdOrders)
if createdOrders != nil { if createdOrders != nil {
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter
} }

View File

@ -21,3 +21,13 @@ func NewOrderError(e error, o Order) error {
order: o, order: o,
} }
} }
type ZeroAssetError struct {
error
}
func NewZeroAssetError(e error) ZeroAssetError {
return ZeroAssetError{
error: e,
}
}

51
pkg/types/filter.go Normal file
View File

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

View File

@ -998,51 +998,6 @@ func Rolling(a Series, window int) *RollingResult {
return &RollingResult{a, window} 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 { type SigmoidResult struct {
a Series a Series
} }