grid2: add order executor mock for testing reverse order

This commit is contained in:
c9s 2022-12-07 14:19:49 +08:00
parent 9215e401d0
commit 9d24540826
3 changed files with 191 additions and 14 deletions

View 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...)
}

View File

@ -28,6 +28,13 @@ func init() {
bbgo.RegisterStrategy(ID, &Strategy{}) 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 { type Strategy struct {
Environment *bbgo.Environment Environment *bbgo.Environment
@ -102,7 +109,7 @@ type Strategy struct {
session *bbgo.ExchangeSession session *bbgo.ExchangeSession
orderQueryService types.ExchangeOrderQueryService orderQueryService types.ExchangeOrderQueryService
orderExecutor *bbgo.GeneralOrderExecutor orderExecutor OrderExecutor
historicalTrades *bbgo.TradeStore historicalTrades *bbgo.TradeStore
// groupID is the group ID used for the strategy instance for canceling orders // groupID is the group ID used for the strategy instance for canceling orders
@ -705,6 +712,12 @@ func (s *Strategy) closeGrid(ctx context.Context) error {
return nil 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 // openGrid
// 1) if quantity or amount is set, we should use quantity/amount directly instead of using investment amount to calculate. // 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. // 2) if baseInvestment, quoteInvestment is set, then we should calculate the quantity from the given base investment and quote investment.
@ -715,9 +728,7 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
return nil return nil
} }
s.grid = NewGrid(s.LowerPrice, s.UpperPrice, fixedpoint.NewFromInt(s.GridNum), s.Market.TickSize) s.grid = s.newGrid()
s.grid.CalculateArithmeticPins()
s.logger.Info("OPENING GRID: ", s.grid.String()) s.logger.Info("OPENING GRID: ", s.grid.String())
lastPrice, err := s.getLastTradePrice(ctx, session) lastPrice, err := s.getLastTradePrice(ctx, session)
@ -957,7 +968,7 @@ func (s *Strategy) checkMinimalQuoteInvestment() error {
return nil 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() instanceID := s.InstanceID()
s.session = session s.session = session
@ -1004,19 +1015,18 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.historicalTrades.EnablePrune = true s.historicalTrades.EnablePrune = true
s.historicalTrades.BindStream(session.UserDataStream) s.historicalTrades.BindStream(session.UserDataStream)
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position) orderExecutor := bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
s.orderExecutor.BindEnvironment(s.Environment) orderExecutor.BindEnvironment(s.Environment)
s.orderExecutor.BindProfitStats(s.ProfitStats) orderExecutor.BindProfitStats(s.ProfitStats)
s.orderExecutor.Bind() orderExecutor.Bind()
orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _, _ fixedpoint.Value) {
s.orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _, _ fixedpoint.Value) {
s.GridProfitStats.AddTrade(trade) s.GridProfitStats.AddTrade(trade)
}) })
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) { orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
bbgo.Sync(ctx, s) bbgo.Sync(ctx, s)
}) })
orderExecutor.ActiveMakerOrders().OnFilled(s.handleOrderFilled)
s.orderExecutor.ActiveMakerOrders().OnFilled(s.handleOrderFilled) s.orderExecutor = orderExecutor
// TODO: detect if there are previous grid orders on the order book // TODO: detect if there are previous grid orders on the order book
if s.ClearOpenOrdersWhenStart { if s.ClearOpenOrdersWhenStart {

View File

@ -15,6 +15,8 @@ import (
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/types/mocks" "github.com/c9s/bbgo/pkg/types/mocks"
gridmocks "github.com/c9s/bbgo/pkg/strategy/grid2/mocks"
) )
func TestStrategy_checkRequiredInvestmentByQuantity(t *testing.T) { func TestStrategy_checkRequiredInvestmentByQuantity(t *testing.T) {
@ -269,6 +271,7 @@ func newTestStrategy() *Strategy {
s := &Strategy{ s := &Strategy{
logger: logrus.NewEntry(logrus.New()), logger: logrus.NewEntry(logrus.New()),
Symbol: "BTCUSDT",
Market: market, Market: market,
GridProfitStats: newGridProfitStats(market), GridProfitStats: newGridProfitStats(market),
UpperPrice: number(20_000), UpperPrice: number(20_000),
@ -397,6 +400,75 @@ func TestStrategy_aggregateOrderBaseFee(t *testing.T) {
assert.Equal(t, "0.01", baseFee.String()) 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,
})
})
}
func TestStrategy_aggregateOrderBaseFeeRetry(t *testing.T) { func TestStrategy_aggregateOrderBaseFeeRetry(t *testing.T) {
s := newTestStrategy() s := newTestStrategy()