2023-12-07 06:38:13 +00:00
package dca2
import (
"context"
2024-04-15 08:25:34 +00:00
"fmt"
2023-12-07 06:38:13 +00:00
2024-05-22 03:37:13 +00:00
"github.com/c9s/bbgo/pkg/exchange/retry"
2023-12-07 06:38:13 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
2024-04-15 08:25:34 +00:00
"github.com/pkg/errors"
2023-12-07 06:38:13 +00:00
)
func ( s * Strategy ) placeTakeProfitOrders ( ctx context . Context ) error {
2024-03-12 06:29:36 +00:00
s . logger . Info ( "start placing take profit orders" )
2024-04-16 05:37:53 +00:00
currentRound , err := s . collector . CollectCurrentRound ( ctx )
2024-04-22 05:46:28 +00:00
if err != nil {
return errors . Wrap ( err , "failed to place the take-profit order when collecting current round" )
}
2024-05-29 08:32:18 +00:00
if len ( currentRound . TakeProfitOrders ) > 0 {
return fmt . Errorf ( "there is a take-profit order before placing the take-profit order, please check it and manually fix it" )
2024-04-15 08:25:34 +00:00
}
2024-04-16 05:37:53 +00:00
trades , err := s . collector . CollectRoundTrades ( ctx , currentRound )
2024-04-15 08:25:34 +00:00
if err != nil {
return errors . Wrap ( err , "failed to place the take-profit order when collecting round trades" )
}
2024-04-15 09:27:56 +00:00
roundPosition := types . NewPositionFromMarket ( s . Market )
2024-09-11 07:40:45 +00:00
roundPosition . SetExchangeFeeRate ( s . ExchangeSession . ExchangeName , types . ExchangeFee {
MakerFeeRate : s . ExchangeSession . MakerFeeRate ,
TakerFeeRate : s . ExchangeSession . TakerFeeRate ,
} )
2024-04-15 08:25:34 +00:00
for _ , trade := range trades {
2024-04-16 08:52:50 +00:00
s . logger . Infof ( "add trade into the position of this round %s" , trade . String ( ) )
2024-04-15 08:25:34 +00:00
if trade . FeeProcessing {
return fmt . Errorf ( "failed to place the take-profit order because there is a trade's fee not ready" )
}
2024-04-15 09:27:56 +00:00
roundPosition . AddTrade ( trade )
2024-04-15 08:25:34 +00:00
}
2024-04-15 09:27:56 +00:00
s . logger . Infof ( "position of this round before place the take-profit order: %s" , roundPosition . String ( ) )
2024-04-15 08:25:34 +00:00
2024-04-15 09:27:56 +00:00
order := generateTakeProfitOrder ( s . Market , s . TakeProfitRatio , roundPosition , s . OrderGroupID )
2024-05-22 03:37:13 +00:00
// verify the volume of order
bals , err := retry . QueryAccountBalancesUntilSuccessfulLite ( ctx , s . ExchangeSession . Exchange )
if err != nil {
return errors . Wrapf ( err , "failed to query balance to verify" )
}
bal , exist := bals [ s . Market . BaseCurrency ]
if ! exist {
return fmt . Errorf ( "there is no %s in the balances %+v" , s . Market . BaseCurrency , bals )
}
quantityDiff := bal . Available . Sub ( order . Quantity )
if quantityDiff . Sign ( ) < 0 {
return fmt . Errorf ( "the balance (%s) is not enough for the order (%s)" , bal . String ( ) , order . Quantity . String ( ) )
}
if quantityDiff . Compare ( s . Market . MinQuantity ) > 0 {
s . logger . Warnf ( "the diff between balance (%s) and the take-profit order (%s) is larger than min quantity %s" , bal . String ( ) , order . Quantity . String ( ) , s . Market . MinQuantity . String ( ) )
}
2023-12-07 06:38:13 +00:00
createdOrders , err := s . OrderExecutor . SubmitOrders ( ctx , order )
if err != nil {
return err
}
for _ , createdOrder := range createdOrders {
s . logger . Info ( "SUBMIT TAKE PROFIT ORDER " , createdOrder . String ( ) )
}
return nil
}
2023-12-22 07:50:48 +00:00
func generateTakeProfitOrder ( market types . Market , takeProfitRatio fixedpoint . Value , position * types . Position , orderGroupID uint32 ) types . SubmitOrder {
2023-12-07 06:38:13 +00:00
side := types . SideTypeSell
takeProfitPrice := market . TruncatePrice ( position . AverageCost . Mul ( fixedpoint . One . Add ( takeProfitRatio ) ) )
return types . SubmitOrder {
Symbol : market . Symbol ,
Market : market ,
Type : types . OrderTypeLimit ,
Price : takeProfitPrice ,
Side : side ,
TimeInForce : types . TimeInForceGTC ,
Quantity : position . GetBase ( ) . Abs ( ) ,
Tag : orderTag ,
GroupID : orderGroupID ,
}
}