mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
Merge pull request #1515 from c9s/kbearXD/dca2/dev-mode
[dca2] add dev mode field for dev
This commit is contained in:
commit
7cc3d1b193
7
pkg/strategy/dca2/dev_mode.go
Normal file
7
pkg/strategy/dca2/dev_mode.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package dca2
|
||||
|
||||
// DevMode, if Enabled is true. it means it will check the running field
|
||||
type DevMode struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
IsNewAccount bool `json:"isNewAccount"`
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/exchange/retry"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
@ -32,6 +33,21 @@ func (s *Strategy) placeOpenPositionOrders(ctx context.Context) error {
|
|||
|
||||
s.debugOrders(createdOrders)
|
||||
|
||||
if s.DevMode != nil && s.DevMode.Enabled && s.DevMode.IsNewAccount {
|
||||
if len(createdOrders) > 0 {
|
||||
s.ProfitStats.FromOrderID = createdOrders[0].OrderID
|
||||
}
|
||||
|
||||
for _, createdOrder := range createdOrders {
|
||||
if s.ProfitStats.FromOrderID > createdOrder.OrderID {
|
||||
s.ProfitStats.FromOrderID = createdOrder.OrderID
|
||||
}
|
||||
}
|
||||
|
||||
s.DevMode.IsNewAccount = false
|
||||
bbgo.Sync(ctx, s)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ func newProfitStats(market types.Market, quoteInvestment fixedpoint.Value) *Prof
|
|||
return &ProfitStats{
|
||||
Symbol: market.Symbol,
|
||||
Market: market,
|
||||
Round: 0,
|
||||
Round: 1,
|
||||
QuoteInvestment: quoteInvestment,
|
||||
CurrentRoundFee: make(map[string]fixedpoint.Value),
|
||||
TotalFee: make(map[string]fixedpoint.Value),
|
||||
|
|
|
@ -7,11 +7,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/core"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
var recoverSinceLimit = time.Date(2024, time.January, 29, 12, 0, 0, 0, time.Local)
|
||||
|
||||
type descendingClosedOrderQueryService interface {
|
||||
QueryClosedOrdersDesc(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) ([]types.Order, error)
|
||||
}
|
||||
|
@ -34,7 +35,7 @@ func (s *Strategy) recover(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
closedOrders, err := queryService.QueryClosedOrdersDesc(ctx, s.Symbol, time.Date(2024, time.January, 12, 14, 0, 0, 0, time.Local), time.Now(), 0)
|
||||
closedOrders, err := queryService.QueryClosedOrdersDesc(ctx, s.Symbol, recoverSinceLimit, time.Now(), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -45,12 +46,11 @@ func (s *Strategy) recover(ctx context.Context) error {
|
|||
}
|
||||
debugRoundOrders(s.logger, "current", currentRound)
|
||||
|
||||
// recover state
|
||||
state, err := recoverState(ctx, s.Symbol, int(s.MaxOrderCount), openOrders, currentRound, s.OrderExecutor.ActiveMakerOrders(), s.OrderExecutor.OrderStore(), s.OrderGroupID)
|
||||
if err != nil {
|
||||
// recover profit stats
|
||||
if err := recoverProfitStats(ctx, s); err != nil {
|
||||
return err
|
||||
}
|
||||
s.logger.Info("recover stats DONE")
|
||||
s.logger.Info("recover profit stats DONE")
|
||||
|
||||
// recover position
|
||||
if err := recoverPosition(ctx, s.Position, queryService, currentRound); err != nil {
|
||||
|
@ -58,73 +58,65 @@ func (s *Strategy) recover(ctx context.Context) error {
|
|||
}
|
||||
s.logger.Info("recover position DONE")
|
||||
|
||||
// recover profit stats
|
||||
recoverProfitStats(ctx, s)
|
||||
s.logger.Info("recover profit stats DONE")
|
||||
|
||||
// recover startTimeOfNextRound
|
||||
startTimeOfNextRound := recoverStartTimeOfNextRound(ctx, currentRound, s.CoolDownInterval)
|
||||
|
||||
s.state = state
|
||||
s.startTimeOfNextRound = startTimeOfNextRound
|
||||
|
||||
// recover state
|
||||
state, err := recoverState(ctx, s.ProfitStats.QuoteInvestment, int(s.MaxOrderCount), currentRound, s.OrderExecutor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.state = state
|
||||
s.logger.Info("recover stats DONE")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// recover state
|
||||
func recoverState(ctx context.Context, symbol string, maxOrderCount int, openOrders []types.Order, currentRound Round, activeOrderBook *bbgo.ActiveOrderBook, orderStore *core.OrderStore, groupID uint32) (State, error) {
|
||||
if len(currentRound.OpenPositionOrders) == 0 {
|
||||
// new strategy
|
||||
func recoverState(ctx context.Context, quoteInvestment fixedpoint.Value, maxOrderCount int, currentRound Round, orderExecutor *bbgo.GeneralOrderExecutor) (State, error) {
|
||||
activeOrderBook := orderExecutor.ActiveMakerOrders()
|
||||
orderStore := orderExecutor.OrderStore()
|
||||
|
||||
// dca stop at take-profit order stage
|
||||
if currentRound.TakeProfitOrder.OrderID != 0 {
|
||||
if len(currentRound.OpenPositionOrders) != maxOrderCount {
|
||||
return None, fmt.Errorf("there is take-profit order but the number of open-position orders (%d) is not the same as maxOrderCount(%d). Please check it", len(currentRound.OpenPositionOrders), maxOrderCount)
|
||||
}
|
||||
|
||||
takeProfitOrder := currentRound.TakeProfitOrder
|
||||
if takeProfitOrder.Status == types.OrderStatusFilled {
|
||||
return WaitToOpenPosition, nil
|
||||
} else if types.IsActiveOrder(takeProfitOrder) {
|
||||
activeOrderBook.Add(takeProfitOrder)
|
||||
orderStore.Add(takeProfitOrder)
|
||||
return TakeProfitReady, nil
|
||||
} else {
|
||||
return None, fmt.Errorf("the status of take-profit order is %s. Please check it", takeProfitOrder.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// dca stop at no take-profit order stage
|
||||
openPositionOrders := currentRound.OpenPositionOrders
|
||||
numOpenPositionOrders := len(openPositionOrders)
|
||||
|
||||
// new strategy
|
||||
if len(openPositionOrders) == 0 {
|
||||
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 {
|
||||
activeOrderBook.Add(openOrders[0])
|
||||
// current round's take-profit order still opened, wait to fill
|
||||
return TakeProfitReady, nil
|
||||
} else {
|
||||
return None, fmt.Errorf("stop at taking profit stage, but the open order's OrderID is not the take-profit order's OrderID")
|
||||
}
|
||||
}
|
||||
|
||||
return None, fmt.Errorf("stop at taking profit stage, but the number of open orders is > 1")
|
||||
}
|
||||
|
||||
numOpenPositionOrders := len(currentRound.OpenPositionOrders)
|
||||
// should not happen
|
||||
if numOpenPositionOrders > maxOrderCount {
|
||||
return None, fmt.Errorf("the number of open-position orders is > max order number")
|
||||
} else if numOpenPositionOrders < maxOrderCount {
|
||||
// The number of open-position orders should be the same as maxOrderCount
|
||||
// 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")
|
||||
}
|
||||
|
||||
if numOpenOrders > numOpenPositionOrders {
|
||||
return None, fmt.Errorf("the number of open orders is > the number of open-position orders")
|
||||
}
|
||||
|
||||
if numOpenOrders == numOpenPositionOrders {
|
||||
activeOrderBook.Add(openOrders...)
|
||||
orderStore.Add(openOrders...)
|
||||
return OpenPositionReady, nil
|
||||
return None, fmt.Errorf("the number of open-position orders (%d) is > max order number", numOpenPositionOrders)
|
||||
}
|
||||
|
||||
// collect open-position orders' status
|
||||
var openedCnt, filledCnt, cancelledCnt int64
|
||||
for _, order := range currentRound.OpenPositionOrders {
|
||||
switch order.Status {
|
||||
case types.OrderStatusNew, types.OrderStatusPartiallyFilled:
|
||||
activeOrderBook.Add(order)
|
||||
orderStore.Add(order)
|
||||
openedCnt++
|
||||
case types.OrderStatusFilled:
|
||||
filledCnt++
|
||||
|
@ -135,21 +127,40 @@ func recoverState(ctx context.Context, symbol string, maxOrderCount int, openOrd
|
|||
}
|
||||
}
|
||||
|
||||
// the number of open-position orders is the same as maxOrderCount -> place open-position orders successfully
|
||||
if numOpenPositionOrders == maxOrderCount {
|
||||
// all open-position orders are still not filled -> OpenPositionReady
|
||||
if filledCnt == 0 && cancelledCnt == 0 {
|
||||
return OpenPositionReady, nil
|
||||
}
|
||||
|
||||
// there are at least one open-position orders filled -> OpenPositionOrderFilled
|
||||
if filledCnt > 0 && cancelledCnt == 0 {
|
||||
return OpenPositionOrderFilled, nil
|
||||
}
|
||||
|
||||
// there are at last one open-position orders cancelled ->
|
||||
if cancelledCnt > 0 {
|
||||
return OpenPositionOrdersCancelling, nil
|
||||
}
|
||||
|
||||
return None, fmt.Errorf("unexpected order status combination when numOpenPositionOrders(%d) == maxOrderCount(%d) (opened, filled, cancelled) = (%d, %d, %d)", numOpenPositionOrders, maxOrderCount, openedCnt, filledCnt, cancelledCnt)
|
||||
}
|
||||
|
||||
// the number of open-position orders is less than maxOrderCount -> failed to place open-position orders
|
||||
// 1. This strategy is 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.....
|
||||
if filledCnt == 0 && cancelledCnt == 0 {
|
||||
// TODO: place the remaining open-position orders
|
||||
return OpenPositionReady, nil
|
||||
}
|
||||
|
||||
if filledCnt > 0 && cancelledCnt == 0 {
|
||||
activeOrderBook.Add(openOrders...)
|
||||
orderStore.Add(openOrders...)
|
||||
// TODO: place the remaing open-position orders and change state to OpenPositionOrderFilled
|
||||
return OpenPositionOrderFilled, nil
|
||||
}
|
||||
|
||||
if openedCnt > 0 && filledCnt > 0 && cancelledCnt > 0 {
|
||||
return OpenPositionOrdersCancelling, nil
|
||||
}
|
||||
|
||||
if openedCnt == 0 && filledCnt > 0 && cancelledCnt > 0 {
|
||||
return OpenPositionOrdersCancelled, nil
|
||||
}
|
||||
|
||||
return None, fmt.Errorf("unexpected order status combination")
|
||||
return None, fmt.Errorf("unexpected order status combination when numOpenPositionOrders(%d) < maxOrderCount(%d) (opened, filled, cancelled) = (%d, %d, %d)", numOpenPositionOrders, maxOrderCount, openedCnt, filledCnt, cancelledCnt)
|
||||
}
|
||||
|
||||
func recoverPosition(ctx context.Context, position *types.Position, queryService RecoverApiQueryService, currentRound Round) error {
|
||||
|
@ -197,29 +208,7 @@ func recoverProfitStats(ctx context.Context, strategy *Strategy) error {
|
|||
return fmt.Errorf("profit stats is nil, please check it")
|
||||
}
|
||||
|
||||
strategy.CalculateAndEmitProfit(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverQuoteInvestment(currentRound Round) fixedpoint.Value {
|
||||
if len(currentRound.OpenPositionOrders) == 0 {
|
||||
return fixedpoint.Zero
|
||||
}
|
||||
|
||||
total := fixedpoint.Zero
|
||||
for _, order := range currentRound.OpenPositionOrders {
|
||||
total = total.Add(order.Quantity.Mul(order.Price))
|
||||
}
|
||||
|
||||
if currentRound.TakeProfitOrder.OrderID != 0 && currentRound.TakeProfitOrder.Status == types.OrderStatusFilled {
|
||||
total = total.Add(currentRound.TakeProfitOrder.Quantity.Mul(currentRound.TakeProfitOrder.Price))
|
||||
for _, order := range currentRound.OpenPositionOrders {
|
||||
total = total.Sub(order.ExecutedQuantity.Mul(order.Price))
|
||||
}
|
||||
}
|
||||
|
||||
return total
|
||||
return strategy.CalculateAndEmitProfit(ctx)
|
||||
}
|
||||
|
||||
func recoverStartTimeOfNextRound(ctx context.Context, currentRound Round, coolDownInterval types.Duration) time.Time {
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/core"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -24,7 +24,7 @@ func generateTestOrder(side types.SideType, status types.OrderStatus, createdAt
|
|||
|
||||
}
|
||||
|
||||
func Test_GetCurrenctAndLastRoundOrders(t *testing.T) {
|
||||
func Test_GetCurrenctRoundOrders(t *testing.T) {
|
||||
t.Run("case 1", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{
|
||||
|
@ -94,85 +94,74 @@ func (m *MockQueryOrders) QueryClosedOrdersDesc(ctx context.Context, symbol stri
|
|||
}
|
||||
|
||||
func Test_RecoverState(t *testing.T) {
|
||||
symbol := "BTCUSDT"
|
||||
strategy := newTestStrategy()
|
||||
quoteInvestment := fixedpoint.MustNewFromString("1000")
|
||||
|
||||
t.Run("new strategy", func(t *testing.T) {
|
||||
openOrders := []types.Order{}
|
||||
currentRound := Round{}
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
position := types.NewPositionFromMarket(strategy.Market)
|
||||
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
|
||||
state, err := recoverState(context.Background(), quoteInvestment, 5, currentRound, orderExecutor)
|
||||
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{
|
||||
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,
|
||||
OpenPositionOrders: []types.Order{
|
||||
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)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
position := types.NewPositionFromMarket(strategy.Market)
|
||||
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
|
||||
state, err := recoverState(context.Background(), quoteInvestment, 5, currentRound, orderExecutor)
|
||||
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{
|
||||
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{
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
openOrders[0],
|
||||
openOrders[1],
|
||||
openOrders[2],
|
||||
openOrders[3],
|
||||
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)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
position := types.NewPositionFromMarket(strategy.Market)
|
||||
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
|
||||
state, err := recoverState(context.Background(), quoteInvestment, 5, currentRound, orderExecutor)
|
||||
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{
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
|
||||
}
|
||||
currentRound := Round{
|
||||
OpenPositionOrders: []types.Order{
|
||||
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],
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
position := types.NewPositionFromMarket(strategy.Market)
|
||||
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
|
||||
state, err := recoverState(context.Background(), quoteInvestment, 5, currentRound, orderExecutor)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, OpenPositionOrdersCancelling, state)
|
||||
})
|
||||
|
||||
t.Run("open-position orders are cancelled", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{}
|
||||
currentRound := Round{
|
||||
OpenPositionOrders: []types.Order{
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
|
@ -182,20 +171,17 @@ func Test_RecoverState(t *testing.T) {
|
|||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
position := types.NewPositionFromMarket(strategy.Market)
|
||||
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
|
||||
state, err := recoverState(context.Background(), quoteInvestment, 5, currentRound, orderExecutor)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, OpenPositionOrdersCancelled, state)
|
||||
assert.Equal(t, OpenPositionOrdersCancelling, state)
|
||||
})
|
||||
|
||||
t.Run("at take profit stage, and not filled yet", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
openOrders := []types.Order{
|
||||
generateTestOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||
}
|
||||
currentRound := Round{
|
||||
TakeProfitOrder: openOrders[0],
|
||||
TakeProfitOrder: generateTestOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||
OpenPositionOrders: []types.Order{
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*time.Second)),
|
||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-2*time.Second)),
|
||||
|
@ -204,16 +190,15 @@ func Test_RecoverState(t *testing.T) {
|
|||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
position := types.NewPositionFromMarket(strategy.Market)
|
||||
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
|
||||
state, err := recoverState(context.Background(), quoteInvestment, 5, currentRound, orderExecutor)
|
||||
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: generateTestOrder(types.SideTypeSell, types.OrderStatusFilled, now),
|
||||
OpenPositionOrders: []types.Order{
|
||||
|
@ -224,9 +209,9 @@ func Test_RecoverState(t *testing.T) {
|
|||
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
|
||||
},
|
||||
}
|
||||
orderStore := core.NewOrderStore(symbol)
|
||||
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
|
||||
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
|
||||
position := types.NewPositionFromMarket(strategy.Market)
|
||||
orderExecutor := bbgo.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
|
||||
state, err := recoverState(context.Background(), quoteInvestment, 5, currentRound, orderExecutor)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, WaitToOpenPosition, state)
|
||||
})
|
||||
|
|
|
@ -66,6 +66,9 @@ type Strategy struct {
|
|||
// UseCancelAllOrdersApiWhenClose uses a different API to cancel all the orders on the market when closing a grid
|
||||
UseCancelAllOrdersApiWhenClose bool `json:"useCancelAllOrdersApiWhenClose"`
|
||||
|
||||
// dev mode
|
||||
DevMode *DevMode `json:"devMode"`
|
||||
|
||||
// log
|
||||
logger *logrus.Entry
|
||||
LogFields logrus.Fields `json:"logFields"`
|
||||
|
@ -141,6 +144,12 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
|||
s.Position = types.NewPositionFromMarket(s.Market)
|
||||
}
|
||||
|
||||
// if dev mode is on and it's not a new strategy
|
||||
if s.DevMode != nil && s.DevMode.Enabled && !s.DevMode.IsNewAccount {
|
||||
s.ProfitStats = newProfitStats(s.Market, s.QuoteInvestment)
|
||||
s.Position = types.NewPositionFromMarket(s.Market)
|
||||
}
|
||||
|
||||
s.Position.Strategy = ID
|
||||
s.Position.StrategyInstanceID = instanceID
|
||||
|
||||
|
@ -202,14 +211,18 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
|||
s.logger.Info("[DCA] user data stream authenticated")
|
||||
time.AfterFunc(3*time.Second, func() {
|
||||
if isInitialize := s.initializeNextStateC(); !isInitialize {
|
||||
if s.RecoverWhenStart {
|
||||
|
||||
// no need to recover when two situation
|
||||
// 1. recoverWhenStart is false
|
||||
// 2. dev mode is on and it's not new strategy
|
||||
if !s.RecoverWhenStart || (s.DevMode != nil && s.DevMode.Enabled && !s.DevMode.IsNewAccount) {
|
||||
s.state = WaitToOpenPosition
|
||||
} else {
|
||||
// recover
|
||||
if err := s.recover(ctx); err != nil {
|
||||
s.logger.WithError(err).Error("[DCA] something wrong when state recovering")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
s.state = WaitToOpenPosition
|
||||
}
|
||||
|
||||
s.logger.Infof("[DCA] state: %d", s.state)
|
||||
|
@ -382,6 +395,8 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error {
|
|||
// store into persistence
|
||||
bbgo.Sync(ctx, s)
|
||||
|
||||
s.logger.Infof("[DCA] profit stats:\n%s", s.ProfitStats.String())
|
||||
|
||||
// emit profit
|
||||
s.EmitProfit(s.ProfitStats)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user