diff --git a/cmd/bbgo-desktop/main.go b/cmd/bbgo-desktop/main.go index fdfefc066..97863b071 100644 --- a/cmd/bbgo-desktop/main.go +++ b/cmd/bbgo-desktop/main.go @@ -114,7 +114,6 @@ func main() { } // for setup mode, we don't start the trader - trader.Subscribe() if err := trader.Run(ctx); err != nil { log.WithError(err).Error("failed to start trader") return diff --git a/go.sum b/go.sum index b1645b58c..84f5ec691 100644 --- a/go.sum +++ b/go.sum @@ -140,7 +140,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= diff --git a/pkg/backtest/matching_test.go b/pkg/backtest/matching_test.go index 30022d70a..4fea47cfb 100644 --- a/pkg/backtest/matching_test.go +++ b/pkg/backtest/matching_test.go @@ -40,7 +40,6 @@ func TestSimplePriceMatching_LimitOrder(t *testing.T) { BaseCurrency: "BTC", MinNotional: 0.001, MinAmount: 10.0, - MinLot: 0.001, MinQuantity: 0.001, } diff --git a/pkg/bbgo/order_execution.go b/pkg/bbgo/order_execution.go index 127f1d8c9..5be6804ff 100644 --- a/pkg/bbgo/order_execution.go +++ b/pkg/bbgo/order_execution.go @@ -6,7 +6,7 @@ import ( "math" "github.com/pkg/errors" - "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" @@ -33,7 +33,7 @@ type ExchangeOrderExecutionRouter struct { func (e *ExchangeOrderExecutionRouter) SubmitOrdersTo(ctx context.Context, session string, orders ...types.SubmitOrder) (types.OrderSlice, error) { es, ok := e.sessions[session] if !ok { - return nil, fmt.Errorf("exchange Session %s not found", session) + return nil, fmt.Errorf("exchange session %s not found", session) } formattedOrders, err := formatOrders(es, orders) @@ -85,7 +85,7 @@ func (e *ExchangeOrderExecutor) SubmitOrders(ctx context.Context, orders ...type e.Notify(":memo: Submitting %s %s %s order with quantity: %s", order.Symbol, order.Type, order.Side, order.QuantityString, order) } - logrus.Infof("submitting order: %s", order.String()) + log.Infof("submitting order: %s", order.String()) } e.notifySubmitOrders(formattedOrders...) @@ -94,7 +94,7 @@ func (e *ExchangeOrderExecutor) SubmitOrders(ctx context.Context, orders ...type } type BasicRiskController struct { - Logger *logrus.Logger + Logger *log.Logger MaxOrderAmount fixedpoint.Value `json:"maxOrderAmount,omitempty"` MinQuoteBalance fixedpoint.Value `json:"minQuoteBalance,omitempty"` @@ -258,12 +258,12 @@ func (c *BasicRiskController) ProcessOrders(session *ExchangeSession, orders ... continue } - if quantity < market.MinLot { + if quantity < market.MinQuantity { addError( fmt.Errorf( "can not place sell order, quantity %f is less than the minimal lot %f, order: %s", quantity, - market.MinLot, + market.MinQuantity, order.String())) continue } diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index adf956302..c2c8690dd 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -148,6 +148,8 @@ func (trader *Trader) Subscribe() { } func (trader *Trader) Run(ctx context.Context) error { + trader.Subscribe() + if err := trader.environment.Init(ctx); err != nil { return err } diff --git a/pkg/cmd/backtest.go b/pkg/cmd/backtest.go index f5765acf9..257966f8d 100644 --- a/pkg/cmd/backtest.go +++ b/pkg/cmd/backtest.go @@ -214,8 +214,6 @@ var BacktestCmd = &cobra.Command{ log.Warnf("backtest does not support CrossExchangeStrategy, strategies won't be added.") } - trader.Subscribe() - if err := trader.Run(ctx); err != nil { return err } diff --git a/pkg/cmd/builtin.go b/pkg/cmd/builtin.go index 538ab6966..d43248862 100644 --- a/pkg/cmd/builtin.go +++ b/pkg/cmd/builtin.go @@ -8,6 +8,7 @@ import ( _ "github.com/c9s/bbgo/pkg/strategy/grid" _ "github.com/c9s/bbgo/pkg/strategy/mirrormaker" _ "github.com/c9s/bbgo/pkg/strategy/pricealert" + _ "github.com/c9s/bbgo/pkg/strategy/sat" _ "github.com/c9s/bbgo/pkg/strategy/swing" _ "github.com/c9s/bbgo/pkg/strategy/trailingstop" _ "github.com/c9s/bbgo/pkg/strategy/xpuremaker" diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index 86503f158..a23f5cd75 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -267,8 +267,6 @@ func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer return err } - trader.Subscribe() - if err := trader.Run(ctx); err != nil { return err } diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index 4b270d5f0..69c56eaac 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -119,10 +119,9 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) { // maxQty defines the maximum quantity/icebergQty allowed. // stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by. if f := symbol.LotSizeFilter(); f != nil { - market.MinLot = util.MustParseFloat(f.MinQuantity) market.MinQuantity = util.MustParseFloat(f.MinQuantity) market.MaxQuantity = util.MustParseFloat(f.MaxQuantity) - // market.StepSize = util.MustParseFloat(f.StepSize) + market.StepSize = util.MustParseFloat(f.StepSize) } if f := symbol.PriceFilter(); f != nil { @@ -438,8 +437,11 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde req := e.Client.NewCreateMarginOrderService(). Symbol(order.Symbol). Type(orderType). - Side(binance.SideType(order.Side)). - NewClientOrderID(clientOrderID) + Side(binance.SideType(order.Side)) + + if len(clientOrderID) > 0 { + req.NewClientOrderID(clientOrderID) + } // use response result format req.NewOrderRespType(binance.NewOrderRespTypeRESULT) @@ -460,15 +462,19 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde req.Quantity(strconv.FormatFloat(order.Quantity, 'f', 8, 64)) } - if len(order.PriceString) > 0 { - req.Price(order.PriceString) - } else if order.Market.Symbol != "" { - req.Price(order.Market.FormatPrice(order.Price)) - } else { - req.Price(strconv.FormatFloat(order.Price, 'f', 8, 64)) + // set price field for limit orders + switch order.Type { + case types.OrderTypeStopLimit, types.OrderTypeLimit: + if len(order.PriceString) > 0 { + req.Price(order.PriceString) + } else if order.Market.Symbol != "" { + req.Price(order.Market.FormatPrice(order.Price)) + } } + // set stop price switch order.Type { + case types.OrderTypeStopLimit, types.OrderTypeStopMarket: if len(order.StopPriceString) == 0 { return nil, fmt.Errorf("stop price string can not be empty") diff --git a/pkg/exchange/binance/stream.go b/pkg/exchange/binance/stream.go index bcf8db377..009778933 100644 --- a/pkg/exchange/binance/stream.go +++ b/pkg/exchange/binance/stream.go @@ -351,7 +351,7 @@ func (s *Stream) read(ctx context.Context) { return default: - if err := s.Conn.SetReadDeadline(time.Now().Add(1 * time.Minute)); err != nil { + if err := s.Conn.SetReadDeadline(time.Now().Add(3 * time.Minute)); err != nil { log.WithError(err).Errorf("set read deadline error: %s", err.Error()) } diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index 0e9c7492b..91843cde7 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -113,12 +113,14 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) { BaseCurrency: toGlobalCurrency(m.BaseUnit), MinNotional: m.MinQuoteAmount, MinAmount: m.MinQuoteAmount, - MinLot: 1.0 / math.Pow10(m.BaseUnitPrecision), // make it like 0.0001 - MinQuantity: m.MinBaseAmount, - MaxQuantity: 10000.0, - MinPrice: 1.0 / math.Pow10(m.QuoteUnitPrecision), // used in the price formatter - MaxPrice: 10000.0, - TickSize: 1.0 / math.Pow10(m.QuoteUnitPrecision), + + MinQuantity: m.MinBaseAmount, + MaxQuantity: 10000.0, + StepSize: 1.0 / math.Pow10(m.BaseUnitPrecision), // make it like 0.0001 + + MinPrice: 1.0 / math.Pow10(m.QuoteUnitPrecision), // used in the price formatter + MaxPrice: 10000.0, + TickSize: 1.0 / math.Pow10(m.QuoteUnitPrecision), } markets[symbol] = market diff --git a/pkg/types/market.go b/pkg/types/market.go index 3ab7a3b98..f94eaf27e 100644 --- a/pkg/types/market.go +++ b/pkg/types/market.go @@ -53,14 +53,15 @@ type Market struct { QuoteCurrency string BaseCurrency string - // The MIN_NOTIONAL filter defines the minimum notional value allowed for an order on a symbol. An order's notional value is the price * quantity + // The MIN_NOTIONAL filter defines the minimum notional value allowed for an order on a symbol. + // An order's notional value is the price * quantity MinNotional float64 MinAmount float64 // The LOT_SIZE filter defines the quantity - MinLot float64 MinQuantity float64 MaxQuantity float64 + StepSize float64 MinPrice float64 MaxPrice float64 @@ -86,17 +87,25 @@ func (m Market) FormatPriceCurrency(val float64) string { func (m Market) FormatPrice(val float64) string { // p := math.Pow10(m.PricePrecision) - prec := int(math.Abs(math.Log10(m.MinPrice))) + return formatPrice(val, m.TickSize) +} + +func formatPrice(price float64, tickSize float64) string { + prec := int(math.Round(math.Abs(math.Log10(tickSize)))) p := math.Pow10(prec) - val = math.Trunc(val*p) / p - return strconv.FormatFloat(val, 'f', prec, 64) + price = math.Trunc(price*p) / p + return strconv.FormatFloat(price, 'f', prec, 64) } func (m Market) FormatQuantity(val float64) string { - prec := int(math.Abs(math.Log10(m.MinLot))) + return formatQuantity(val, m.StepSize) +} + +func formatQuantity(quantity float64, lot float64) string { + prec := int(math.Round(math.Abs(math.Log10(lot)))) p := math.Pow10(prec) - val = math.Trunc(val*p) / p - return strconv.FormatFloat(val, 'f', prec, 64) + quantity = math.Trunc(quantity*p) / p + return strconv.FormatFloat(quantity, 'f', prec, 64) } func (m Market) FormatVolume(val float64) string { diff --git a/pkg/types/market_test.go b/pkg/types/market_test.go index dd1225f2f..146b15399 100644 --- a/pkg/types/market_test.go +++ b/pkg/types/market_test.go @@ -8,6 +8,22 @@ import ( "github.com/stretchr/testify/assert" ) +func TestFormatQuantity(t *testing.T) { + quantity := formatQuantity(0.12511, 0.01) + assert.Equal(t, "0.12", quantity) + + quantity = formatQuantity(0.12511, 0.001) + assert.Equal(t, "0.125", quantity) +} + +func TestFormatPrice(t *testing.T) { + price := formatPrice(26.288256, 0.0001) + assert.Equal(t, "26.2882", price) + + price = formatPrice(26.288656, 0.001) + assert.Equal(t, "26.288", price) +} + func TestDurationParse(t *testing.T) { type A struct { Duration Duration `json:"duration"`