mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
use stateTransition
This commit is contained in:
parent
b30b023858
commit
59b1bb68cb
|
@ -3,7 +3,6 @@ package dca2
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -13,15 +12,19 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
type queryAPI interface {
|
||||
QueryOpenOrders(ctx context.Context, symbol string) ([]types.Order, error)
|
||||
type descendingClosedOrderQueryService interface {
|
||||
QueryClosedOrdersDesc(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) ([]types.Order, error)
|
||||
QueryOrderTrades(ctx context.Context, q types.OrderQuery) ([]types.Trade, error)
|
||||
}
|
||||
|
||||
type RecoverApiQueryService interface {
|
||||
types.ExchangeOrderQueryService
|
||||
types.ExchangeTradeService
|
||||
descendingClosedOrderQueryService
|
||||
}
|
||||
|
||||
func (s *Strategy) recover(ctx context.Context) error {
|
||||
s.logger.Info("[DCA] recover")
|
||||
queryService, ok := s.Session.Exchange.(queryAPI)
|
||||
queryService, ok := s.Session.Exchange.(RecoverApiQueryService)
|
||||
if !ok {
|
||||
return fmt.Errorf("[DCA] exchange %s doesn't support queryAPI interface", s.Session.ExchangeName)
|
||||
}
|
||||
|
@ -70,9 +73,19 @@ func (s *Strategy) recover(ctx context.Context) error {
|
|||
|
||||
// recover state
|
||||
func recoverState(ctx context.Context, symbol string, short bool, maxOrderNum int, openOrders []types.Order, currentRound Round, activeOrderBook *bbgo.ActiveOrderBook, orderStore *core.OrderStore, groupID uint32) (State, error) {
|
||||
if len(currentRound.OpenPositionOrders) == 0 {
|
||||
// new strategy
|
||||
return WaitToOpenPosition, nil
|
||||
}
|
||||
|
||||
numOpenOrders := len(openOrders)
|
||||
// dca stop at take profit order stage
|
||||
if currentRound.TakeProfitOrder.OrderID != 0 {
|
||||
if numOpenOrders == 0 {
|
||||
// current round's take-profit order filled, wait to open next round
|
||||
return WaitToOpenPosition, nil
|
||||
}
|
||||
|
||||
// check the open orders is take profit order or not
|
||||
if numOpenOrders == 1 {
|
||||
if openOrders[0].OrderID == currentRound.TakeProfitOrder.OrderID {
|
||||
|
@ -84,24 +97,17 @@ func recoverState(ctx context.Context, symbol string, short bool, maxOrderNum in
|
|||
}
|
||||
}
|
||||
|
||||
if numOpenOrders == 0 {
|
||||
// current round's take-profit order filled, wait to open next round
|
||||
return WaitToOpenPosition, nil
|
||||
}
|
||||
|
||||
return None, fmt.Errorf("stop at taking profit stage, but the number of open orders is > 1")
|
||||
}
|
||||
|
||||
if len(currentRound.OpenPositionOrders) == 0 {
|
||||
// new strategy
|
||||
return WaitToOpenPosition, nil
|
||||
}
|
||||
|
||||
numOpenPositionOrders := len(currentRound.OpenPositionOrders)
|
||||
if numOpenPositionOrders > maxOrderNum {
|
||||
return None, fmt.Errorf("the number of open-position orders is > max order number")
|
||||
} else if numOpenPositionOrders < maxOrderNum {
|
||||
// failed to place some orders at open position stage
|
||||
// The number of open-position orders should be the same as maxOrderNum
|
||||
// If not, it may be the following possible cause
|
||||
// 1. This strategy at position opening, so it may not place all orders we want successfully
|
||||
// 2. There are some errors when placing open-position orders. e.g. cannot lock fund.....
|
||||
return None, fmt.Errorf("the number of open-position orders is < max order number")
|
||||
}
|
||||
|
||||
|
@ -146,7 +152,7 @@ func recoverState(ctx context.Context, symbol string, short bool, maxOrderNum in
|
|||
return None, fmt.Errorf("unexpected order status combination")
|
||||
}
|
||||
|
||||
func recoverPosition(ctx context.Context, position *types.Position, queryService queryAPI, currentRound Round) error {
|
||||
func recoverPosition(ctx context.Context, position *types.Position, queryService RecoverApiQueryService, currentRound Round) error {
|
||||
if position == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -232,9 +238,7 @@ func getCurrentRoundOrders(short bool, openOrders, closedOrders []types.Order, g
|
|||
allOrders = append(allOrders, openOrders...)
|
||||
allOrders = append(allOrders, closedOrders...)
|
||||
|
||||
sort.Slice(allOrders, func(i, j int) bool {
|
||||
return allOrders[i].CreationTime.After(allOrders[j].CreationTime.Time())
|
||||
})
|
||||
types.SortOrdersDescending(allOrders)
|
||||
|
||||
var currentRound Round
|
||||
lastSide := takeProfitSide
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func generateOrder(side types.SideType, status types.OrderStatus, createdAt time.Time) types.Order {
|
||||
func generateTestOrder(side types.SideType, status types.OrderStatus, createdAt time.Time) types.Order {
|
||||
return types.Order{
|
||||
OrderID: rand.Uint64(),
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
|
@ -25,60 +25,58 @@ func generateOrder(side types.SideType, status types.OrderStatus, createdAt time
|
|||
}
|
||||
|
||||
func Test_GetCurrenctAndLastRoundOrders(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
t.Run("case 1", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{
|
||||
generateOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||
generateTestOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||
}
|
||||
|
||||
closedOrders := []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-2*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-3*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-4*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-5*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-2*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-3*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-4*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-5*time.Second)),
|
||||
}
|
||||
|
||||
currentRound, err := getCurrentRoundOrders(false, openOrders, closedOrders, 0)
|
||||
|
||||
assert.NoError(err)
|
||||
assert.NotEqual(0, currentRound.TakeProfitOrder.OrderID)
|
||||
assert.Equal(5, len(currentRound.OpenPositionOrders))
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, 0, currentRound.TakeProfitOrder.OrderID)
|
||||
assert.Equal(t, 5, len(currentRound.OpenPositionOrders))
|
||||
})
|
||||
|
||||
t.Run("case 2", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{
|
||||
generateOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||
generateTestOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||
}
|
||||
|
||||
closedOrders := []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-2*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-3*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-4*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-5*time.Second)),
|
||||
generateOrder(types.SideTypeSell, types.OrderStatusFilled, now.Add(-6*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-7*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-8*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-9*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-10*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-11*time.Second)),
|
||||
generateOrder(types.SideTypeSell, types.OrderStatusFilled, now.Add(-12*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-13*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-14*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-15*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-16*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-17*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-2*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-3*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-4*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-5*time.Second)),
|
||||
generateTestOrder(types.SideTypeSell, types.OrderStatusFilled, now.Add(-6*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-7*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-8*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-9*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-10*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-11*time.Second)),
|
||||
generateTestOrder(types.SideTypeSell, types.OrderStatusFilled, now.Add(-12*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-13*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-14*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-15*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-16*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-17*time.Second)),
|
||||
}
|
||||
|
||||
currentRound, err := getCurrentRoundOrders(false, openOrders, closedOrders, 0)
|
||||
|
||||
assert.NoError(err)
|
||||
assert.NotEqual(0, currentRound.TakeProfitOrder.OrderID)
|
||||
assert.Equal(5, len(currentRound.OpenPositionOrders))
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, 0, currentRound.TakeProfitOrder.OrderID)
|
||||
assert.Equal(t, 5, len(currentRound.OpenPositionOrders))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -96,7 +94,6 @@ func (m *MockQueryOrders) QueryClosedOrdersDesc(ctx context.Context, symbol stri
|
|||
}
|
||||
|
||||
func Test_RecoverState(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
symbol := "BTCUSDT"
|
||||
|
||||
t.Run("new strategy", func(t *testing.T) {
|
||||
|
@ -105,18 +102,18 @@ func Test_RecoverState(t *testing.T) {
|
|||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
assert.NoError(err)
|
||||
assert.Equal(WaitToOpenPosition, state)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, WaitToOpenPosition, state)
|
||||
})
|
||||
|
||||
t.Run("at open position stage and no filled order", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusPartiallyFilled, now.Add(-1*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-2*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-3*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-4*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusPartiallyFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-2*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-3*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-4*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
|
||||
}
|
||||
currentRound := Round{
|
||||
OpenPositionOrders: openOrders,
|
||||
|
@ -124,21 +121,21 @@ func Test_RecoverState(t *testing.T) {
|
|||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
assert.NoError(err)
|
||||
assert.Equal(OpenPositionReady, state)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, OpenPositionReady, state)
|
||||
})
|
||||
|
||||
t.Run("at open position stage and there at least one filled order", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-2*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-3*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-4*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-2*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-3*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-4*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
|
||||
}
|
||||
currentRound := Round{
|
||||
OpenPositionOrders: []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
openOrders[0],
|
||||
openOrders[1],
|
||||
openOrders[2],
|
||||
|
@ -148,29 +145,29 @@ func Test_RecoverState(t *testing.T) {
|
|||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
assert.NoError(err)
|
||||
assert.Equal(OpenPositionOrderFilled, state)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, OpenPositionOrderFilled, state)
|
||||
})
|
||||
|
||||
t.Run("open position stage finish, but stop at cancelling", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
|
||||
}
|
||||
currentRound := Round{
|
||||
OpenPositionOrders: []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-3*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-4*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-3*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-4*time.Second)),
|
||||
openOrders[0],
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
assert.NoError(err)
|
||||
assert.Equal(OpenPositionOrdersCancelling, state)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, OpenPositionOrdersCancelling, state)
|
||||
})
|
||||
|
||||
t.Run("open-position orders are cancelled", func(t *testing.T) {
|
||||
|
@ -178,59 +175,59 @@ func Test_RecoverState(t *testing.T) {
|
|||
openOrders := []types.Order{}
|
||||
currentRound := Round{
|
||||
OpenPositionOrders: []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-3*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-4*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-3*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-4*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
assert.NoError(err)
|
||||
assert.Equal(OpenPositionOrdersCancelled, state)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, OpenPositionOrdersCancelled, state)
|
||||
})
|
||||
|
||||
t.Run("at take profit stage, and not filled yet", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{
|
||||
generateOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||
generateTestOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||
}
|
||||
currentRound := Round{
|
||||
TakeProfitOrder: openOrders[0],
|
||||
OpenPositionOrders: []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-3*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-4*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-3*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-4*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
assert.NoError(err)
|
||||
assert.Equal(TakeProfitReady, state)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, TakeProfitReady, state)
|
||||
})
|
||||
|
||||
t.Run("at take profit stage, take-profit order filled", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{}
|
||||
currentRound := Round{
|
||||
TakeProfitOrder: generateOrder(types.SideTypeSell, types.OrderStatusFilled, now),
|
||||
TakeProfitOrder: generateTestOrder(types.SideTypeSell, types.OrderStatusFilled, now),
|
||||
OpenPositionOrders: []types.Order{
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-3*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-4*time.Second)),
|
||||
generateOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-3*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-4*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
assert.NoError(err)
|
||||
assert.Equal(WaitToOpenPosition, state)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, WaitToOpenPosition, state)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,6 +18,16 @@ const (
|
|||
TakeProfitReady
|
||||
)
|
||||
|
||||
var stateTransition map[State]State = map[State]State{
|
||||
WaitToOpenPosition: PositionOpening,
|
||||
PositionOpening: OpenPositionReady,
|
||||
OpenPositionReady: OpenPositionOrderFilled,
|
||||
OpenPositionOrderFilled: OpenPositionOrdersCancelling,
|
||||
OpenPositionOrdersCancelling: OpenPositionOrdersCancelled,
|
||||
OpenPositionOrdersCancelled: TakeProfitReady,
|
||||
TakeProfitReady: WaitToOpenPosition,
|
||||
}
|
||||
|
||||
func (s *Strategy) initializeNextStateC() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
@ -63,6 +73,19 @@ func (s *Strategy) runState(ctx context.Context) {
|
|||
s.triggerNextState()
|
||||
case nextState := <-s.nextStateC:
|
||||
s.logger.Infof("[DCA] currenct state: %d, next state: %d", s.state, nextState)
|
||||
|
||||
// check the next state is valid
|
||||
validNextState, exist := stateTransition[s.state]
|
||||
if !exist {
|
||||
s.logger.Warnf("[DCA] %d not in stateTransition", s.state)
|
||||
continue
|
||||
}
|
||||
|
||||
if nextState != validNextState {
|
||||
s.logger.Warnf("[DCA] %d is not valid next state of curreny state %d", nextState, s.state)
|
||||
}
|
||||
|
||||
// move to next state
|
||||
switch s.state {
|
||||
case WaitToOpenPosition:
|
||||
s.runWaitToOpenPositionState(ctx, nextState)
|
||||
|
@ -85,28 +108,20 @@ func (s *Strategy) runState(ctx context.Context) {
|
|||
|
||||
func (s *Strategy) triggerNextState() {
|
||||
switch s.state {
|
||||
case WaitToOpenPosition:
|
||||
s.nextStateC <- PositionOpening
|
||||
case PositionOpening:
|
||||
s.nextStateC <- OpenPositionReady
|
||||
case OpenPositionReady:
|
||||
// trigger from order filled event
|
||||
// only trigger from order filled event
|
||||
case OpenPositionOrderFilled:
|
||||
// trigger from kline event
|
||||
case OpenPositionOrdersCancelling:
|
||||
s.nextStateC <- OpenPositionOrdersCancelled
|
||||
case OpenPositionOrdersCancelled:
|
||||
s.nextStateC <- TakeProfitReady
|
||||
// only trigger from kline event
|
||||
case TakeProfitReady:
|
||||
// trigger from order filled event
|
||||
// only trigger from order filled event
|
||||
default:
|
||||
if nextState, ok := stateTransition[s.state]; ok {
|
||||
s.nextStateC <- nextState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) runWaitToOpenPositionState(_ context.Context, next State) {
|
||||
if next != PositionOpening {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("[State] WaitToOpenPosition - check startTimeOfNextRound")
|
||||
if time.Now().Before(s.startTimeOfNextRound) {
|
||||
return
|
||||
|
@ -117,10 +132,6 @@ func (s *Strategy) runWaitToOpenPositionState(_ context.Context, next State) {
|
|||
}
|
||||
|
||||
func (s *Strategy) runPositionOpening(ctx context.Context, next State) {
|
||||
if next != OpenPositionReady {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("[State] PositionOpening - start placing open-position orders")
|
||||
if err := s.placeOpenPositionOrders(ctx); err != nil {
|
||||
s.logger.WithError(err).Error("failed to place dca orders, please check it.")
|
||||
|
@ -131,17 +142,11 @@ func (s *Strategy) runPositionOpening(ctx context.Context, next State) {
|
|||
}
|
||||
|
||||
func (s *Strategy) runOpenPositionReady(_ context.Context, next State) {
|
||||
if next != OpenPositionOrderFilled {
|
||||
return
|
||||
}
|
||||
s.state = OpenPositionOrderFilled
|
||||
s.logger.Info("[State] OpenPositionReady -> OpenPositionOrderFilled")
|
||||
}
|
||||
|
||||
func (s *Strategy) runOpenPositionOrderFilled(_ context.Context, next State) {
|
||||
if next != OpenPositionOrdersCancelling {
|
||||
return
|
||||
}
|
||||
s.state = OpenPositionOrdersCancelling
|
||||
s.logger.Info("[State] OpenPositionOrderFilled -> OpenPositionOrdersCancelling")
|
||||
|
||||
|
@ -150,10 +155,6 @@ func (s *Strategy) runOpenPositionOrderFilled(_ context.Context, next State) {
|
|||
}
|
||||
|
||||
func (s *Strategy) runOpenPositionOrdersCancelling(ctx context.Context, next State) {
|
||||
if next != OpenPositionOrdersCancelled {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("[State] OpenPositionOrdersCancelling - start cancelling open-position orders")
|
||||
if err := s.cancelOpenPositionOrders(ctx); err != nil {
|
||||
s.logger.WithError(err).Error("failed to cancel maker orders")
|
||||
|
@ -167,10 +168,6 @@ func (s *Strategy) runOpenPositionOrdersCancelling(ctx context.Context, next Sta
|
|||
}
|
||||
|
||||
func (s *Strategy) runOpenPositionOrdersCancelled(ctx context.Context, next State) {
|
||||
if next != TakeProfitReady {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("[State] OpenPositionOrdersCancelled - start placing take-profit orders")
|
||||
if err := s.placeTakeProfitOrders(ctx); err != nil {
|
||||
s.logger.WithError(err).Error("failed to open take profit orders")
|
||||
|
@ -181,10 +178,6 @@ func (s *Strategy) runOpenPositionOrdersCancelled(ctx context.Context, next Stat
|
|||
}
|
||||
|
||||
func (s *Strategy) runTakeProfitReady(_ context.Context, next State) {
|
||||
if next != WaitToOpenPosition {
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("[State] TakeProfitReady - start reseting position and calculate budget for next round")
|
||||
if s.Short {
|
||||
s.Budget = s.Budget.Add(s.Position.Base)
|
||||
|
|
|
@ -20,6 +20,14 @@ func SortOrdersAscending(orders []Order) []Order {
|
|||
return orders
|
||||
}
|
||||
|
||||
// SortOrdersDescending sorts by creation time descending-ly
|
||||
func SortOrdersDescending(orders []Order) []Order {
|
||||
sort.Slice(orders, func(i, j int) bool {
|
||||
return orders[i].CreationTime.Time().After(orders[j].CreationTime.Time())
|
||||
})
|
||||
return orders
|
||||
}
|
||||
|
||||
// SortOrdersByPrice sorts by creation time ascending-ly
|
||||
func SortOrdersByPrice(orders []Order, descending bool) []Order {
|
||||
var f func(i, j int) bool
|
||||
|
|
Loading…
Reference in New Issue
Block a user