mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
FEATURE: [dca2] make the take-profit order of round from order to orders
This commit is contained in:
parent
7bde48adce
commit
1d0b4e5cb8
|
@ -2,7 +2,6 @@ package dca2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -11,9 +10,15 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Round contains the open-position orders and the take-profit orders
|
||||||
|
// 1. len(OpenPositionOrders) == 0 -> not open position
|
||||||
|
// 2. len(TakeProfitOrders) == 0 -> not in the take-profit stage
|
||||||
|
// 3. There are take-profit orders only when open-position orders are cancelled
|
||||||
|
// 4. We need to make sure the order: open-position (BUY) -> take-profit (SELL) -> open-position (BUY) -> take-profit (SELL) -> ...
|
||||||
|
// 5. When there is one filled take-profit order, this round must be finished. We need to verify all take-profit orders are not active
|
||||||
type Round struct {
|
type Round struct {
|
||||||
OpenPositionOrders []types.Order
|
OpenPositionOrders []types.Order
|
||||||
TakeProfitOrder types.Order
|
TakeProfitOrders []types.Order
|
||||||
}
|
}
|
||||||
|
|
||||||
type Collector struct {
|
type Collector struct {
|
||||||
|
@ -106,10 +111,7 @@ func (rc Collector) CollectCurrentRound(ctx context.Context) (Round, error) {
|
||||||
case openPositionSide:
|
case openPositionSide:
|
||||||
currentRound.OpenPositionOrders = append(currentRound.OpenPositionOrders, order)
|
currentRound.OpenPositionOrders = append(currentRound.OpenPositionOrders, order)
|
||||||
case takeProfitSide:
|
case takeProfitSide:
|
||||||
if currentRound.TakeProfitOrder.OrderID != 0 {
|
currentRound.TakeProfitOrders = append(currentRound.TakeProfitOrders, order)
|
||||||
return currentRound, fmt.Errorf("there are two take-profit orders in one round, please check it")
|
|
||||||
}
|
|
||||||
currentRound.TakeProfitOrder = order
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,12 +143,20 @@ func (rc *Collector) CollectFinishRounds(ctx context.Context, fromOrderID uint64
|
||||||
case types.SideTypeBuy:
|
case types.SideTypeBuy:
|
||||||
round.OpenPositionOrders = append(round.OpenPositionOrders, order)
|
round.OpenPositionOrders = append(round.OpenPositionOrders, order)
|
||||||
case types.SideTypeSell:
|
case types.SideTypeSell:
|
||||||
|
round.TakeProfitOrders = append(round.TakeProfitOrders, order)
|
||||||
|
|
||||||
if order.Status != types.OrderStatusFilled {
|
if order.Status != types.OrderStatusFilled {
|
||||||
rc.logger.Infof("take-profit order is %s not filled, so this round is not finished. Skip it", order.Status)
|
rc.logger.Infof("take-profit order is %s not filled, so this round is not finished. Keep collecting", order.Status)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
round.TakeProfitOrder = order
|
for _, o := range round.TakeProfitOrders {
|
||||||
|
if types.IsActiveOrder(o) {
|
||||||
|
// Should not happen ! but we only log it
|
||||||
|
rc.logger.Errorf("there is at least one take-profit order (%d) is still active, please check it", o.OrderID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rounds = append(rounds, round)
|
rounds = append(rounds, round)
|
||||||
round = Round{}
|
round = Round{}
|
||||||
default:
|
default:
|
||||||
|
@ -164,10 +174,7 @@ func (rc *Collector) CollectRoundTrades(ctx context.Context, round Round) ([]typ
|
||||||
var roundTrades []types.Trade
|
var roundTrades []types.Trade
|
||||||
var roundOrders []types.Order = round.OpenPositionOrders
|
var roundOrders []types.Order = round.OpenPositionOrders
|
||||||
|
|
||||||
// if the take-profit order's OrderID == 0 -> no take-profit order.
|
roundOrders = append(roundOrders, round.TakeProfitOrders...)
|
||||||
if round.TakeProfitOrder.OrderID != 0 {
|
|
||||||
roundOrders = append(roundOrders, round.TakeProfitOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, order := range roundOrders {
|
for _, order := range roundOrders {
|
||||||
if order.ExecutedQuantity.IsZero() {
|
if order.ExecutedQuantity.IsZero() {
|
||||||
|
|
72
pkg/strategy/dca2/collector_test.go
Normal file
72
pkg/strategy/dca2/collector_test.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package dca2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/c9s/bbgo/pkg/types/mocks"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_NewCollector(t *testing.T) {
|
||||||
|
symbol := "ETHUSDT"
|
||||||
|
logger := log.WithField("strategy", ID)
|
||||||
|
|
||||||
|
t.Run("return nil if the exchange doesn't support ExchangeTradeHistoryService", func(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
mockEx := mocks.NewMockExchange(mockCtrl)
|
||||||
|
mockEx.EXPECT().Name().Return(types.ExchangeMax)
|
||||||
|
|
||||||
|
collector := NewCollector(logger, symbol, 0, mockEx)
|
||||||
|
|
||||||
|
assert.Nil(t, collector)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("return nil if the exchange doesn't support ExchangeOrderQueryService", func(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
mockEx := mocks.NewMockExchange(mockCtrl)
|
||||||
|
mockEx.EXPECT().Name().Return(types.ExchangeMax)
|
||||||
|
|
||||||
|
mockTradeHistoryService := mocks.NewMockExchangeTradeHistoryService(mockCtrl)
|
||||||
|
|
||||||
|
type TestEx struct {
|
||||||
|
types.Exchange
|
||||||
|
types.ExchangeTradeHistoryService
|
||||||
|
}
|
||||||
|
|
||||||
|
ex := TestEx{
|
||||||
|
Exchange: mockEx,
|
||||||
|
ExchangeTradeHistoryService: mockTradeHistoryService,
|
||||||
|
}
|
||||||
|
|
||||||
|
collector := NewCollector(logger, symbol, 0, ex)
|
||||||
|
|
||||||
|
assert.Nil(t, collector)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("return nil if the exchange doesn't support descendingClosedOrderQueryService", func(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
mockEx := mocks.NewMockExchange(mockCtrl)
|
||||||
|
mockEx.EXPECT().Name().Return(types.ExchangeMax)
|
||||||
|
|
||||||
|
mockTradeHistoryService := mocks.NewMockExchangeTradeHistoryService(mockCtrl)
|
||||||
|
mockOrderQueryService := mocks.NewMockExchangeOrderQueryService(mockCtrl)
|
||||||
|
|
||||||
|
type TestEx struct {
|
||||||
|
types.Exchange
|
||||||
|
types.ExchangeTradeHistoryService
|
||||||
|
types.ExchangeOrderQueryService
|
||||||
|
}
|
||||||
|
|
||||||
|
ex := TestEx{
|
||||||
|
Exchange: mockEx,
|
||||||
|
ExchangeTradeHistoryService: mockTradeHistoryService,
|
||||||
|
ExchangeOrderQueryService: mockOrderQueryService,
|
||||||
|
}
|
||||||
|
|
||||||
|
collector := NewCollector(logger, symbol, 0, ex)
|
||||||
|
|
||||||
|
assert.Nil(t, collector)
|
||||||
|
})
|
||||||
|
}
|
|
@ -22,7 +22,9 @@ func (s *Strategy) debugOrders(submitOrders []types.Order) {
|
||||||
func debugRoundOrders(logger *logrus.Entry, roundName string, round Round) {
|
func debugRoundOrders(logger *logrus.Entry, roundName string, round Round) {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString("ROUND " + roundName + " [\n")
|
sb.WriteString("ROUND " + roundName + " [\n")
|
||||||
sb.WriteString(round.TakeProfitOrder.String() + "\n")
|
for i, order := range round.TakeProfitOrders {
|
||||||
|
sb.WriteString(fmt.Sprintf("%3d) ", i+1) + order.String() + "\n")
|
||||||
|
}
|
||||||
sb.WriteString("------------------------------------------------\n")
|
sb.WriteString("------------------------------------------------\n")
|
||||||
for i, order := range round.OpenPositionOrders {
|
for i, order := range round.OpenPositionOrders {
|
||||||
sb.WriteString(fmt.Sprintf("%3d) ", i+1) + order.String() + "\n")
|
sb.WriteString(fmt.Sprintf("%3d) ", i+1) + order.String() + "\n")
|
||||||
|
|
|
@ -64,63 +64,55 @@ func recoverState(ctx context.Context, maxOrderCount int, currentRound Round, or
|
||||||
orderStore := orderExecutor.OrderStore()
|
orderStore := orderExecutor.OrderStore()
|
||||||
|
|
||||||
// dca stop at take-profit order stage
|
// dca stop at take-profit order stage
|
||||||
if currentRound.TakeProfitOrder.OrderID != 0 {
|
if len(currentRound.TakeProfitOrders) > 0 {
|
||||||
// the number of open-positions orders may not be equal to maxOrderCount, because the notional may not enough to open maxOrderCount orders
|
openedOrders, cancelledOrders, filledOrders, unexpectedOrders := classifyOrders(currentRound.TakeProfitOrders)
|
||||||
if len(currentRound.OpenPositionOrders) > maxOrderCount {
|
|
||||||
return None, fmt.Errorf("there is take-profit order but the number of open-position orders (%d) is greater than maxOrderCount(%d). Please check it", len(currentRound.OpenPositionOrders), maxOrderCount)
|
if len(unexpectedOrders) > 0 {
|
||||||
|
return None, fmt.Errorf("there is unexpected status in orders %+v", unexpectedOrders)
|
||||||
}
|
}
|
||||||
|
|
||||||
takeProfitOrder := currentRound.TakeProfitOrder
|
if len(filledOrders) > 0 && len(openedOrders) == 0 {
|
||||||
if takeProfitOrder.Status == types.OrderStatusFilled {
|
|
||||||
return WaitToOpenPosition, nil
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(filledOrders) == 0 && len(openedOrders) > 0 {
|
||||||
|
// add opened order into order store
|
||||||
|
for _, order := range openedOrders {
|
||||||
|
activeOrderBook.Add(order)
|
||||||
|
orderStore.Add(order)
|
||||||
|
}
|
||||||
|
return TakeProfitReady, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return None, fmt.Errorf("the classify orders count is not expected (opened: %d, cancelled: %d, filled: %d)", len(openedOrders), len(cancelledOrders), len(filledOrders))
|
||||||
}
|
}
|
||||||
|
|
||||||
// dca stop at no take-profit order stage
|
// dca stop at no take-profit order stage
|
||||||
openPositionOrders := currentRound.OpenPositionOrders
|
openPositionOrders := currentRound.OpenPositionOrders
|
||||||
numOpenPositionOrders := len(openPositionOrders)
|
|
||||||
|
|
||||||
// new strategy
|
// new strategy
|
||||||
if len(openPositionOrders) == 0 {
|
if len(openPositionOrders) == 0 {
|
||||||
return WaitToOpenPosition, nil
|
return WaitToOpenPosition, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// should not happen
|
|
||||||
if numOpenPositionOrders > maxOrderCount {
|
|
||||||
return None, fmt.Errorf("the number of open-position orders (%d) is > max order number", numOpenPositionOrders)
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect open-position orders' status
|
// collect open-position orders' status
|
||||||
var openedCnt, filledCnt, cancelledCnt int64
|
openedOrders, cancelledOrders, filledOrders, unexpectedOrders := classifyOrders(currentRound.OpenPositionOrders)
|
||||||
for _, order := range currentRound.OpenPositionOrders {
|
if len(unexpectedOrders) > 0 {
|
||||||
switch order.Status {
|
return None, fmt.Errorf("there is unexpected status of orders %+v", unexpectedOrders)
|
||||||
case types.OrderStatusNew, types.OrderStatusPartiallyFilled:
|
}
|
||||||
|
for _, order := range openedOrders {
|
||||||
activeOrderBook.Add(order)
|
activeOrderBook.Add(order)
|
||||||
orderStore.Add(order)
|
orderStore.Add(order)
|
||||||
openedCnt++
|
|
||||||
case types.OrderStatusFilled:
|
|
||||||
filledCnt++
|
|
||||||
case types.OrderStatusCanceled:
|
|
||||||
cancelledCnt++
|
|
||||||
default:
|
|
||||||
return None, fmt.Errorf("there is unexpected status %s of order %s", order.Status, order)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// no order is filled -> OpenPositionReady
|
// no order is filled -> OpenPositionReady
|
||||||
if filledCnt == 0 {
|
if len(filledOrders) == 0 {
|
||||||
return OpenPositionReady, nil
|
return OpenPositionReady, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// there are at least one open-position orders filled
|
// there are at least one open-position orders filled
|
||||||
if cancelledCnt == 0 {
|
if len(cancelledOrders) == 0 {
|
||||||
if openedCnt > 0 {
|
if len(openedOrders) > 0 {
|
||||||
return OpenPositionOrderFilled, nil
|
return OpenPositionOrderFilled, nil
|
||||||
} else {
|
} else {
|
||||||
// all open-position orders filled, change to cancelling and place the take-profit order
|
// all open-position orders filled, change to cancelling and place the take-profit order
|
||||||
|
@ -141,13 +133,18 @@ func recoverPosition(ctx context.Context, position *types.Position, currentRound
|
||||||
position.Reset()
|
position.Reset()
|
||||||
|
|
||||||
var positionOrders []types.Order
|
var positionOrders []types.Order
|
||||||
if currentRound.TakeProfitOrder.OrderID != 0 {
|
|
||||||
// if the take-profit order is already filled, the position is 0
|
var filledCnt int64
|
||||||
if !types.IsActiveOrder(currentRound.TakeProfitOrder) {
|
for _, order := range currentRound.TakeProfitOrders {
|
||||||
return nil
|
if !types.IsActiveOrder(order) {
|
||||||
|
filledCnt++
|
||||||
|
}
|
||||||
|
positionOrders = append(positionOrders, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
positionOrders = append(positionOrders, currentRound.TakeProfitOrder)
|
// all take-profit orders are filled
|
||||||
|
if len(currentRound.TakeProfitOrders) > 0 && filledCnt == int64(len(currentRound.TakeProfitOrders)) {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, order := range currentRound.OpenPositionOrders {
|
for _, order := range currentRound.OpenPositionOrders {
|
||||||
|
@ -184,9 +181,30 @@ func recoverProfitStats(ctx context.Context, strategy *Strategy) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func recoverStartTimeOfNextRound(ctx context.Context, currentRound Round, coolDownInterval types.Duration) time.Time {
|
func recoverStartTimeOfNextRound(ctx context.Context, currentRound Round, coolDownInterval types.Duration) time.Time {
|
||||||
if currentRound.TakeProfitOrder.OrderID != 0 && currentRound.TakeProfitOrder.Status == types.OrderStatusFilled {
|
var startTimeOfNextRound time.Time
|
||||||
return currentRound.TakeProfitOrder.UpdateTime.Time().Add(coolDownInterval.Duration())
|
|
||||||
|
for _, order := range currentRound.TakeProfitOrders {
|
||||||
|
if t := order.UpdateTime.Time().Add(coolDownInterval.Duration()); t.After(startTimeOfNextRound) {
|
||||||
|
startTimeOfNextRound = t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Time{}
|
return startTimeOfNextRound
|
||||||
|
}
|
||||||
|
|
||||||
|
func classifyOrders(orders []types.Order) (opened, cancelled, filled, unexpected []types.Order) {
|
||||||
|
for _, order := range orders {
|
||||||
|
switch order.Status {
|
||||||
|
case types.OrderStatusNew, types.OrderStatusPartiallyFilled:
|
||||||
|
opened = append(opened, order)
|
||||||
|
case types.OrderStatusFilled:
|
||||||
|
filled = append(filled, order)
|
||||||
|
case types.OrderStatusCanceled:
|
||||||
|
cancelled = append(cancelled, order)
|
||||||
|
default:
|
||||||
|
unexpected = append(unexpected, order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return opened, cancelled, filled, unexpected
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,19 +23,6 @@ func generateTestOrder(side types.SideType, status types.OrderStatus, createdAt
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockQueryOrders struct {
|
|
||||||
OpenOrders []types.Order
|
|
||||||
ClosedOrders []types.Order
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockQueryOrders) QueryOpenOrders(ctx context.Context, symbol string) ([]types.Order, error) {
|
|
||||||
return m.OpenOrders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockQueryOrders) QueryClosedOrdersDesc(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) ([]types.Order, error) {
|
|
||||||
return m.ClosedOrders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_RecoverState(t *testing.T) {
|
func Test_RecoverState(t *testing.T) {
|
||||||
strategy := newTestStrategy()
|
strategy := newTestStrategy()
|
||||||
|
|
||||||
|
@ -123,7 +110,9 @@ func Test_RecoverState(t *testing.T) {
|
||||||
t.Run("at take profit stage, and not filled yet", func(t *testing.T) {
|
t.Run("at take profit stage, and not filled yet", func(t *testing.T) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
currentRound := Round{
|
currentRound := Round{
|
||||||
TakeProfitOrder: generateTestOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
TakeProfitOrders: []types.Order{
|
||||||
|
generateTestOrder(types.SideTypeSell, types.OrderStatusNew, now),
|
||||||
|
},
|
||||||
OpenPositionOrders: []types.Order{
|
OpenPositionOrders: []types.Order{
|
||||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*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(-2*time.Second)),
|
||||||
|
@ -142,7 +131,9 @@ func Test_RecoverState(t *testing.T) {
|
||||||
t.Run("at take profit stage, take-profit order filled", func(t *testing.T) {
|
t.Run("at take profit stage, take-profit order filled", func(t *testing.T) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
currentRound := Round{
|
currentRound := Round{
|
||||||
TakeProfitOrder: generateTestOrder(types.SideTypeSell, types.OrderStatusFilled, now),
|
TakeProfitOrders: []types.Order{
|
||||||
|
generateTestOrder(types.SideTypeSell, types.OrderStatusFilled, now),
|
||||||
|
},
|
||||||
OpenPositionOrders: []types.Order{
|
OpenPositionOrders: []types.Order{
|
||||||
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-1*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(-2*time.Second)),
|
||||||
|
@ -158,3 +149,24 @@ func Test_RecoverState(t *testing.T) {
|
||||||
assert.Equal(t, WaitToOpenPosition, state)
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -481,7 +481,11 @@ func (s *Strategy) UpdateProfitStats(ctx context.Context) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update profit stats FromOrderID to make sure we will not collect duplicated rounds
|
// update profit stats FromOrderID to make sure we will not collect duplicated rounds
|
||||||
s.ProfitStats.FromOrderID = round.TakeProfitOrder.OrderID + 1
|
for _, order := range round.TakeProfitOrders {
|
||||||
|
if order.OrderID >= s.ProfitStats.FromOrderID {
|
||||||
|
s.ProfitStats.FromOrderID = order.OrderID + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update quote investment
|
// update quote investment
|
||||||
s.ProfitStats.QuoteInvestment = s.ProfitStats.QuoteInvestment.Add(s.ProfitStats.CurrentRoundProfit)
|
s.ProfitStats.QuoteInvestment = s.ProfitStats.QuoteInvestment.Add(s.ProfitStats.CurrentRoundProfit)
|
||||||
|
|
|
@ -17,8 +17,8 @@ func (s *Strategy) placeTakeProfitOrders(ctx context.Context) error {
|
||||||
return errors.Wrap(err, "failed to place the take-profit order when collecting current round")
|
return errors.Wrap(err, "failed to place the take-profit order when collecting current round")
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentRound.TakeProfitOrder.OrderID != 0 {
|
if len(currentRound.TakeProfitOrders) > 0 {
|
||||||
return fmt.Errorf("there is a take-profit order before placing the take-profit order, please check it")
|
return fmt.Errorf("there is a take-profit order before placing the take-profit order, please check it and manually fix it")
|
||||||
}
|
}
|
||||||
|
|
||||||
trades, err := s.collector.CollectRoundTrades(ctx, currentRound)
|
trades, err := s.collector.CollectRoundTrades(ctx, currentRound)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user