mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #1022 from c9s/feature/grid2
strategy: grid2: more refactoring, fix bugs and add more tests
This commit is contained in:
commit
37f4435aba
|
@ -36,7 +36,7 @@ exchangeStrategies:
|
|||
symbol: BTCUSDT
|
||||
upperPrice: 18_000.0
|
||||
lowerPrice: 16_000.0
|
||||
gridNumber: 200
|
||||
gridNumber: 100
|
||||
|
||||
## compound is used for buying more inventory when the profit is made by the filled SELL order.
|
||||
## when compound is disabled, fixed quantity is used for each grid order.
|
||||
|
@ -70,12 +70,12 @@ exchangeStrategies:
|
|||
# amount: 10.0
|
||||
|
||||
## 2) fixed quantity: it will use your balance to place orders with the fixed quantity. e.g. 0.001 BTC
|
||||
# quantity: 0.001
|
||||
quantity: 0.001
|
||||
|
||||
## 3) quoteInvestment and baseInvestment: when using quoteInvestment, the strategy will automatically calculate your best quantity for the whole grid.
|
||||
## quoteInvestment is required, and baseInvestment is optional (could be zero)
|
||||
## if you have existing BTC position and want to reuse it you can set the baseInvestment.
|
||||
quoteInvestment: 10_000
|
||||
# quoteInvestment: 10_000
|
||||
# baseInvestment: 1.0
|
||||
|
||||
feeRate: 0.075%
|
||||
|
|
95
pkg/strategy/grid2/mocks/order_executor.go
Normal file
95
pkg/strategy/grid2/mocks/order_executor.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/c9s/bbgo/pkg/strategy/grid2 (interfaces: OrderExecutor)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
fixedpoint "github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
types "github.com/c9s/bbgo/pkg/types"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockOrderExecutor is a mock of OrderExecutor interface.
|
||||
type MockOrderExecutor struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockOrderExecutorMockRecorder
|
||||
}
|
||||
|
||||
// MockOrderExecutorMockRecorder is the mock recorder for MockOrderExecutor.
|
||||
type MockOrderExecutorMockRecorder struct {
|
||||
mock *MockOrderExecutor
|
||||
}
|
||||
|
||||
// NewMockOrderExecutor creates a new mock instance.
|
||||
func NewMockOrderExecutor(ctrl *gomock.Controller) *MockOrderExecutor {
|
||||
mock := &MockOrderExecutor{ctrl: ctrl}
|
||||
mock.recorder = &MockOrderExecutorMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockOrderExecutor) EXPECT() *MockOrderExecutorMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// ClosePosition mocks base method.
|
||||
func (m *MockOrderExecutor) ClosePosition(arg0 context.Context, arg1 fixedpoint.Value, arg2 ...string) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "ClosePosition", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ClosePosition indicates an expected call of ClosePosition.
|
||||
func (mr *MockOrderExecutorMockRecorder) ClosePosition(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClosePosition", reflect.TypeOf((*MockOrderExecutor)(nil).ClosePosition), varargs...)
|
||||
}
|
||||
|
||||
// GracefulCancel mocks base method.
|
||||
func (m *MockOrderExecutor) GracefulCancel(arg0 context.Context, arg1 ...types.Order) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "GracefulCancel", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GracefulCancel indicates an expected call of GracefulCancel.
|
||||
func (mr *MockOrderExecutorMockRecorder) GracefulCancel(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GracefulCancel", reflect.TypeOf((*MockOrderExecutor)(nil).GracefulCancel), varargs...)
|
||||
}
|
||||
|
||||
// SubmitOrders mocks base method.
|
||||
func (m *MockOrderExecutor) SubmitOrders(arg0 context.Context, arg1 ...types.SubmitOrder) (types.OrderSlice, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "SubmitOrders", varargs...)
|
||||
ret0, _ := ret[0].(types.OrderSlice)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SubmitOrders indicates an expected call of SubmitOrders.
|
||||
func (mr *MockOrderExecutorMockRecorder) SubmitOrders(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitOrders", reflect.TypeOf((*MockOrderExecutor)(nil).SubmitOrders), varargs...)
|
||||
}
|
|
@ -28,6 +28,13 @@ func init() {
|
|||
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||
}
|
||||
|
||||
//go:generate mockgen -destination=mocks/order_executor.go -package=mocks . OrderExecutor
|
||||
type OrderExecutor interface {
|
||||
SubmitOrders(ctx context.Context, submitOrders ...types.SubmitOrder) (types.OrderSlice, error)
|
||||
ClosePosition(ctx context.Context, percentage fixedpoint.Value, tags ...string) error
|
||||
GracefulCancel(ctx context.Context, orders ...types.Order) error
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Environment *bbgo.Environment
|
||||
|
||||
|
@ -102,7 +109,7 @@ type Strategy struct {
|
|||
session *bbgo.ExchangeSession
|
||||
orderQueryService types.ExchangeOrderQueryService
|
||||
|
||||
orderExecutor *bbgo.GeneralOrderExecutor
|
||||
orderExecutor OrderExecutor
|
||||
historicalTrades *bbgo.TradeStore
|
||||
|
||||
// groupID is the group ID used for the strategy instance for canceling orders
|
||||
|
@ -132,31 +139,12 @@ func (s *Strategy) Validate() error {
|
|||
return fmt.Errorf("gridNum can not be zero")
|
||||
}
|
||||
|
||||
if s.FeeRate.IsZero() {
|
||||
s.FeeRate = fixedpoint.NewFromFloat(0.1 * 0.01) // 0.1%, 0.075% with BNB
|
||||
if err := s.checkSpread(); err != nil {
|
||||
return errors.Wrapf(err, "spread is too small, please try to reduce your gridNum or increase the price range (upperPrice and lowerPrice)")
|
||||
}
|
||||
|
||||
if !s.ProfitSpread.IsZero() {
|
||||
// the min fee rate from 2 maker/taker orders (with 0.1 rate for profit)
|
||||
gridFeeRate := s.FeeRate.Mul(fixedpoint.NewFromFloat(2.01))
|
||||
|
||||
if s.ProfitSpread.Div(s.LowerPrice).Compare(gridFeeRate) < 0 {
|
||||
return fmt.Errorf("profitSpread %f %s is too small for lower price, less than the fee rate: %s", s.ProfitSpread.Float64(), s.ProfitSpread.Div(s.LowerPrice).Percentage(), s.FeeRate.Percentage())
|
||||
}
|
||||
|
||||
if s.ProfitSpread.Div(s.UpperPrice).Compare(gridFeeRate) < 0 {
|
||||
return fmt.Errorf("profitSpread %f %s is too small for upper price, less than the fee rate: %s", s.ProfitSpread.Float64(), s.ProfitSpread.Div(s.UpperPrice).Percentage(), s.FeeRate.Percentage())
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.QuantityOrAmount.Validate(); err != nil {
|
||||
if s.QuoteInvestment.IsZero() && s.BaseInvestment.IsZero() {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !s.QuantityOrAmount.IsSet() && s.QuoteInvestment.IsZero() && s.BaseInvestment.IsZero() {
|
||||
return fmt.Errorf("one of quantity, amount, quoteInvestment must be set")
|
||||
if !s.QuantityOrAmount.IsSet() && s.QuoteInvestment.IsZero() {
|
||||
return fmt.Errorf("either quantity, amount or quoteInvestment must be set")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -171,6 +159,32 @@ func (s *Strategy) InstanceID() string {
|
|||
return fmt.Sprintf("%s-%s-%d-%d-%d", ID, s.Symbol, s.GridNum, s.UpperPrice.Int(), s.LowerPrice.Int())
|
||||
}
|
||||
|
||||
func (s *Strategy) checkSpread() error {
|
||||
gridNum := fixedpoint.NewFromInt(s.GridNum)
|
||||
spread := s.ProfitSpread
|
||||
if spread.IsZero() {
|
||||
spread = s.UpperPrice.Sub(s.LowerPrice).Div(gridNum)
|
||||
}
|
||||
|
||||
feeRate := s.FeeRate
|
||||
if feeRate.IsZero() {
|
||||
feeRate = fixedpoint.NewFromFloat(0.075 * 0.01)
|
||||
}
|
||||
|
||||
// the min fee rate from 2 maker/taker orders (with 0.1 rate for profit)
|
||||
gridFeeRate := feeRate.Mul(fixedpoint.NewFromFloat(2.01))
|
||||
|
||||
if spread.Div(s.LowerPrice).Compare(gridFeeRate) < 0 {
|
||||
return fmt.Errorf("profitSpread %f %s is too small for lower price, less than the grid fee rate: %s", spread.Float64(), spread.Div(s.LowerPrice).Percentage(), gridFeeRate.Percentage())
|
||||
}
|
||||
|
||||
if spread.Div(s.UpperPrice).Compare(gridFeeRate) < 0 {
|
||||
return fmt.Errorf("profitSpread %f %s is too small for upper price, less than the grid fee rate: %s", spread.Float64(), spread.Div(s.UpperPrice).Percentage(), gridFeeRate.Percentage())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) handleOrderCanceled(o types.Order) {
|
||||
s.logger.Infof("GRID ORDER CANCELED: %s", o.String())
|
||||
|
||||
|
@ -606,7 +620,7 @@ func (s *Strategy) calculateQuoteBaseInvestmentQuantity(quoteInvestment, baseInv
|
|||
|
||||
quoteSideQuantity := quoteInvestment.Div(totalQuotePrice)
|
||||
if maxNumberOfSellOrders > 0 {
|
||||
return fixedpoint.Max(quoteSideQuantity, maxBaseQuantity), nil
|
||||
return fixedpoint.Min(quoteSideQuantity, maxBaseQuantity), nil
|
||||
}
|
||||
|
||||
return quoteSideQuantity, nil
|
||||
|
@ -698,6 +712,12 @@ func (s *Strategy) closeGrid(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) newGrid() *Grid {
|
||||
grid := NewGrid(s.LowerPrice, s.UpperPrice, fixedpoint.NewFromInt(s.GridNum), s.Market.TickSize)
|
||||
grid.CalculateArithmeticPins()
|
||||
return grid
|
||||
}
|
||||
|
||||
// openGrid
|
||||
// 1) if quantity or amount is set, we should use quantity/amount directly instead of using investment amount to calculate.
|
||||
// 2) if baseInvestment, quoteInvestment is set, then we should calculate the quantity from the given base investment and quote investment.
|
||||
|
@ -708,9 +728,7 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
|
|||
return nil
|
||||
}
|
||||
|
||||
s.grid = NewGrid(s.LowerPrice, s.UpperPrice, fixedpoint.NewFromInt(s.GridNum), s.Market.TickSize)
|
||||
s.grid.CalculateArithmeticPins()
|
||||
|
||||
s.grid = s.newGrid()
|
||||
s.logger.Info("OPENING GRID: ", s.grid.String())
|
||||
|
||||
lastPrice, err := s.getLastTradePrice(ctx, session)
|
||||
|
@ -950,7 +968,7 @@ func (s *Strategy) checkMinimalQuoteInvestment() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
instanceID := s.InstanceID()
|
||||
|
||||
s.session = session
|
||||
|
@ -997,19 +1015,18 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.historicalTrades.EnablePrune = true
|
||||
s.historicalTrades.BindStream(session.UserDataStream)
|
||||
|
||||
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||
s.orderExecutor.BindEnvironment(s.Environment)
|
||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||
s.orderExecutor.Bind()
|
||||
|
||||
s.orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _, _ fixedpoint.Value) {
|
||||
orderExecutor := bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||
orderExecutor.BindEnvironment(s.Environment)
|
||||
orderExecutor.BindProfitStats(s.ProfitStats)
|
||||
orderExecutor.Bind()
|
||||
orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _, _ fixedpoint.Value) {
|
||||
s.GridProfitStats.AddTrade(trade)
|
||||
})
|
||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||
orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||
bbgo.Sync(ctx, s)
|
||||
})
|
||||
|
||||
s.orderExecutor.ActiveMakerOrders().OnFilled(s.handleOrderFilled)
|
||||
orderExecutor.ActiveMakerOrders().OnFilled(s.handleOrderFilled)
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
// TODO: detect if there are previous grid orders on the order book
|
||||
if s.ClearOpenOrdersWhenStart {
|
||||
|
|
|
@ -15,6 +15,8 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/c9s/bbgo/pkg/types/mocks"
|
||||
|
||||
gridmocks "github.com/c9s/bbgo/pkg/strategy/grid2/mocks"
|
||||
)
|
||||
|
||||
func TestStrategy_checkRequiredInvestmentByQuantity(t *testing.T) {
|
||||
|
@ -269,6 +271,7 @@ func newTestStrategy() *Strategy {
|
|||
|
||||
s := &Strategy{
|
||||
logger: logrus.NewEntry(logrus.New()),
|
||||
Symbol: "BTCUSDT",
|
||||
Market: market,
|
||||
GridProfitStats: newGridProfitStats(market),
|
||||
UpperPrice: number(20_000),
|
||||
|
@ -397,6 +400,326 @@ func TestStrategy_aggregateOrderBaseFee(t *testing.T) {
|
|||
assert.Equal(t, "0.01", baseFee.String())
|
||||
}
|
||||
|
||||
func TestStrategy_handleOrderFilled(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("no fee token", func(t *testing.T) {
|
||||
gridQuantity := number(0.1)
|
||||
orderID := uint64(1)
|
||||
|
||||
s := newTestStrategy()
|
||||
s.Quantity = gridQuantity
|
||||
s.grid = s.newGrid()
|
||||
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockService := mocks.NewMockExchangeOrderQueryService(mockCtrl)
|
||||
mockService.EXPECT().QueryOrderTrades(ctx, types.OrderQuery{
|
||||
Symbol: "BTCUSDT",
|
||||
OrderID: "1",
|
||||
}).Return([]types.Trade{
|
||||
{
|
||||
ID: 1,
|
||||
OrderID: orderID,
|
||||
Exchange: "binance",
|
||||
Price: number(11000.0),
|
||||
Quantity: gridQuantity,
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeBuy,
|
||||
IsBuyer: true,
|
||||
FeeCurrency: "BTC",
|
||||
Fee: number(gridQuantity.Float64() * 0.1 * 0.01),
|
||||
},
|
||||
}, nil)
|
||||
|
||||
s.orderQueryService = mockService
|
||||
|
||||
expectedSubmitOrder := types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Type: types.OrderTypeLimit,
|
||||
Price: number(12_000.0),
|
||||
Quantity: number(0.0999),
|
||||
Side: types.SideTypeSell,
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
Market: s.Market,
|
||||
Tag: "grid",
|
||||
}
|
||||
|
||||
orderExecutor := gridmocks.NewMockOrderExecutor(mockCtrl)
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, expectedSubmitOrder).Return([]types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder},
|
||||
}, nil)
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
s.handleOrderFilled(types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeBuy,
|
||||
Type: types.OrderTypeLimit,
|
||||
Quantity: gridQuantity,
|
||||
Price: number(11000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
},
|
||||
Exchange: "binance",
|
||||
OrderID: orderID,
|
||||
Status: types.OrderStatusFilled,
|
||||
ExecutedQuantity: gridQuantity,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with fee token", func(t *testing.T) {
|
||||
gridQuantity := number(0.1)
|
||||
orderID := uint64(1)
|
||||
|
||||
s := newTestStrategy()
|
||||
s.Quantity = gridQuantity
|
||||
s.grid = s.newGrid()
|
||||
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockService := mocks.NewMockExchangeOrderQueryService(mockCtrl)
|
||||
mockService.EXPECT().QueryOrderTrades(ctx, types.OrderQuery{
|
||||
Symbol: "BTCUSDT",
|
||||
OrderID: "1",
|
||||
}).Return([]types.Trade{
|
||||
{
|
||||
ID: 1,
|
||||
OrderID: orderID,
|
||||
Exchange: "binance",
|
||||
Price: number(11000.0),
|
||||
Quantity: gridQuantity,
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeBuy,
|
||||
IsBuyer: true,
|
||||
FeeCurrency: "BTC",
|
||||
Fee: fixedpoint.Zero,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
s.orderQueryService = mockService
|
||||
|
||||
expectedSubmitOrder := types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Type: types.OrderTypeLimit,
|
||||
Price: number(12_000.0),
|
||||
Quantity: gridQuantity,
|
||||
Side: types.SideTypeSell,
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
Market: s.Market,
|
||||
Tag: "grid",
|
||||
}
|
||||
|
||||
orderExecutor := gridmocks.NewMockOrderExecutor(mockCtrl)
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, expectedSubmitOrder).Return([]types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder},
|
||||
}, nil)
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
s.handleOrderFilled(types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeBuy,
|
||||
Type: types.OrderTypeLimit,
|
||||
Quantity: gridQuantity,
|
||||
Price: number(11000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
},
|
||||
Exchange: "binance",
|
||||
OrderID: orderID,
|
||||
Status: types.OrderStatusFilled,
|
||||
ExecutedQuantity: gridQuantity,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with fee token and EarnBase", func(t *testing.T) {
|
||||
gridQuantity := number(0.1)
|
||||
orderID := uint64(1)
|
||||
|
||||
s := newTestStrategy()
|
||||
s.Quantity = gridQuantity
|
||||
s.EarnBase = true
|
||||
s.grid = s.newGrid()
|
||||
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockService := mocks.NewMockExchangeOrderQueryService(mockCtrl)
|
||||
mockService.EXPECT().QueryOrderTrades(ctx, types.OrderQuery{
|
||||
Symbol: "BTCUSDT",
|
||||
OrderID: "1",
|
||||
}).Return([]types.Trade{
|
||||
{
|
||||
ID: 1,
|
||||
OrderID: orderID,
|
||||
Exchange: "binance",
|
||||
Price: number(11000.0),
|
||||
Quantity: gridQuantity,
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeBuy,
|
||||
IsBuyer: true,
|
||||
FeeCurrency: "BTC",
|
||||
Fee: fixedpoint.Zero,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
s.orderQueryService = mockService
|
||||
|
||||
orderExecutor := gridmocks.NewMockOrderExecutor(mockCtrl)
|
||||
|
||||
expectedSubmitOrder := types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Type: types.OrderTypeLimit,
|
||||
Side: types.SideTypeSell,
|
||||
Price: number(12_000.0),
|
||||
Quantity: number(0.09166666),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
Market: s.Market,
|
||||
Tag: "grid",
|
||||
}
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, expectedSubmitOrder).Return([]types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder},
|
||||
}, nil)
|
||||
|
||||
expectedSubmitOrder2 := types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Type: types.OrderTypeLimit,
|
||||
Side: types.SideTypeBuy,
|
||||
Price: number(11_000.0),
|
||||
Quantity: number(0.09999999),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
Market: s.Market,
|
||||
Tag: "grid",
|
||||
}
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, expectedSubmitOrder2).Return([]types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder2},
|
||||
}, nil)
|
||||
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
s.handleOrderFilled(types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeBuy,
|
||||
Type: types.OrderTypeLimit,
|
||||
Quantity: gridQuantity,
|
||||
Price: number(11000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
},
|
||||
Exchange: "binance",
|
||||
OrderID: 1,
|
||||
Status: types.OrderStatusFilled,
|
||||
ExecutedQuantity: gridQuantity,
|
||||
})
|
||||
|
||||
s.handleOrderFilled(types.Order{
|
||||
SubmitOrder: expectedSubmitOrder,
|
||||
Exchange: "binance",
|
||||
OrderID: 2,
|
||||
Status: types.OrderStatusFilled,
|
||||
ExecutedQuantity: expectedSubmitOrder.Quantity,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with fee token and compound", func(t *testing.T) {
|
||||
gridQuantity := number(0.1)
|
||||
orderID := uint64(1)
|
||||
|
||||
s := newTestStrategy()
|
||||
s.Quantity = gridQuantity
|
||||
s.Compound = true
|
||||
s.grid = s.newGrid()
|
||||
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockService := mocks.NewMockExchangeOrderQueryService(mockCtrl)
|
||||
mockService.EXPECT().QueryOrderTrades(ctx, types.OrderQuery{
|
||||
Symbol: "BTCUSDT",
|
||||
OrderID: "1",
|
||||
}).Return([]types.Trade{
|
||||
{
|
||||
ID: 1,
|
||||
OrderID: orderID,
|
||||
Exchange: "binance",
|
||||
Price: number(11000.0),
|
||||
Quantity: gridQuantity,
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeBuy,
|
||||
IsBuyer: true,
|
||||
FeeCurrency: "BTC",
|
||||
Fee: fixedpoint.Zero,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
s.orderQueryService = mockService
|
||||
|
||||
expectedSubmitOrder := types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Type: types.OrderTypeLimit,
|
||||
Price: number(12_000.0),
|
||||
Quantity: gridQuantity,
|
||||
Side: types.SideTypeSell,
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
Market: s.Market,
|
||||
Tag: "grid",
|
||||
}
|
||||
|
||||
orderExecutor := gridmocks.NewMockOrderExecutor(mockCtrl)
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, expectedSubmitOrder).Return([]types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder},
|
||||
}, nil)
|
||||
|
||||
expectedSubmitOrder2 := types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Type: types.OrderTypeLimit,
|
||||
Price: number(11_000.0),
|
||||
Quantity: number(0.1090909),
|
||||
Side: types.SideTypeBuy,
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
Market: s.Market,
|
||||
Tag: "grid",
|
||||
}
|
||||
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, expectedSubmitOrder2).Return([]types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder2},
|
||||
}, nil)
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
s.handleOrderFilled(types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeBuy,
|
||||
Type: types.OrderTypeLimit,
|
||||
Quantity: gridQuantity,
|
||||
Price: number(11000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
},
|
||||
Exchange: "binance",
|
||||
OrderID: 1,
|
||||
Status: types.OrderStatusFilled,
|
||||
ExecutedQuantity: gridQuantity,
|
||||
})
|
||||
|
||||
s.handleOrderFilled(types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Symbol: "BTCUSDT",
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeLimit,
|
||||
Quantity: gridQuantity,
|
||||
Price: number(12000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
},
|
||||
Exchange: "binance",
|
||||
OrderID: 2,
|
||||
Status: types.OrderStatusFilled,
|
||||
ExecutedQuantity: gridQuantity,
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestStrategy_aggregateOrderBaseFeeRetry(t *testing.T) {
|
||||
s := newTestStrategy()
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user