qbtrade/pkg/strategy/dca2/recover_test.go

173 lines
7.4 KiB
Go
Raw Normal View History

2024-06-27 14:42:38 +00:00
package dca2
import (
"context"
"math/rand"
"testing"
"time"
"git.qtrade.icu/lychiyu/qbtrade/pkg/qbtrade"
"git.qtrade.icu/lychiyu/qbtrade/pkg/types"
"github.com/stretchr/testify/assert"
)
func generateTestOrder(side types.SideType, status types.OrderStatus, createdAt time.Time) types.Order {
return types.Order{
OrderID: rand.Uint64(),
SubmitOrder: types.SubmitOrder{
Side: side,
},
Status: status,
CreationTime: types.Time(createdAt),
}
}
func Test_RecoverState(t *testing.T) {
strategy := newTestStrategy()
t.Run("new strategy", func(t *testing.T) {
currentRound := Round{}
position := types.NewPositionFromMarket(strategy.Market)
orderExecutor := qbtrade.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
state, err := recoverState(context.Background(), 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()
currentRound := Round{
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)),
},
}
position := types.NewPositionFromMarket(strategy.Market)
orderExecutor := qbtrade.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
state, err := recoverState(context.Background(), 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()
currentRound := Round{
OpenPositionOrders: []types.Order{
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, 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)),
},
}
position := types.NewPositionFromMarket(strategy.Market)
orderExecutor := qbtrade.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
state, err := recoverState(context.Background(), 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()
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)),
generateTestOrder(types.SideTypeBuy, types.OrderStatusNew, now.Add(-5*time.Second)),
},
}
position := types.NewPositionFromMarket(strategy.Market)
orderExecutor := qbtrade.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
state, err := recoverState(context.Background(), 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()
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)),
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
},
}
position := types.NewPositionFromMarket(strategy.Market)
orderExecutor := qbtrade.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
state, err := recoverState(context.Background(), 5, currentRound, orderExecutor)
assert.NoError(t, err)
assert.Equal(t, OpenPositionOrdersCancelling, state)
})
t.Run("at take profit stage, and not filled yet", func(t *testing.T) {
now := time.Now()
currentRound := Round{
TakeProfitOrders: []types.Order{
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)),
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)),
},
}
position := types.NewPositionFromMarket(strategy.Market)
orderExecutor := qbtrade.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
state, err := recoverState(context.Background(), 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()
currentRound := Round{
TakeProfitOrders: []types.Order{
generateTestOrder(types.SideTypeSell, types.OrderStatusFilled, now),
},
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)),
generateTestOrder(types.SideTypeBuy, types.OrderStatusCanceled, now.Add(-5*time.Second)),
},
}
position := types.NewPositionFromMarket(strategy.Market)
orderExecutor := qbtrade.NewGeneralOrderExecutor(nil, strategy.Symbol, ID, "", position)
state, err := recoverState(context.Background(), 5, currentRound, orderExecutor)
assert.NoError(t, err)
assert.Equal(t, WaitToOpenPosition, state)
})
}
func Test_classifyOrders(t *testing.T) {
orders := []types.Order{
types.Order{Status: types.OrderStatusCanceled},
types.Order{Status: types.OrderStatusFilled},
types.Order{Status: types.OrderStatusCanceled},
types.Order{Status: types.OrderStatusFilled},
types.Order{Status: types.OrderStatusPartiallyFilled},
types.Order{Status: types.OrderStatusCanceled},
types.Order{Status: types.OrderStatusPartiallyFilled},
types.Order{Status: types.OrderStatusNew},
types.Order{Status: types.OrderStatusRejected},
types.Order{Status: types.OrderStatusCanceled},
}
opened, cancelled, filled, unexpected := classifyOrders(orders)
assert.Equal(t, 3, len(opened))
assert.Equal(t, 4, len(cancelled))
assert.Equal(t, 2, len(filled))
assert.Equal(t, 1, len(unexpected))
}