bbgo_origin/pkg/strategy/dca2/open_position.go

137 lines
3.9 KiB
Go
Raw Normal View History

package dca2
import (
"context"
"fmt"
"github.com/c9s/bbgo/pkg/bbgo"
2023-12-06 08:16:17 +00:00
"github.com/c9s/bbgo/pkg/exchange/retry"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
type cancelOrdersByGroupIDApi interface {
CancelOrdersByGroupID(ctx context.Context, groupID int64) ([]types.Order, error)
}
func (s *Strategy) placeOpenPositionOrders(ctx context.Context) error {
2024-03-12 06:29:36 +00:00
s.logger.Infof("start placing open position orders")
price, err := getBestPriceUntilSuccess(ctx, s.ExchangeSession.Exchange, s.Symbol)
if err != nil {
return err
}
orders, err := generateOpenPositionOrders(s.Market, s.EnableQuoteInvestmentReallocate, s.QuoteInvestment, s.ProfitStats.TotalProfit, price, s.PriceDeviation, s.MaxOrderCount, s.OrderGroupID)
if err != nil {
return err
}
createdOrders, err := s.OrderExecutor.SubmitOrders(ctx, orders...)
if err != nil {
return err
}
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
}
2023-12-22 07:50:48 +00:00
func getBestPriceUntilSuccess(ctx context.Context, ex types.Exchange, symbol string) (fixedpoint.Value, error) {
2023-12-06 08:16:17 +00:00
ticker, err := retry.QueryTickerUntilSuccessful(ctx, ex, symbol)
if err != nil {
return fixedpoint.Zero, err
}
2023-12-22 07:50:48 +00:00
return ticker.Sell, nil
}
func generateOpenPositionOrders(market types.Market, enableQuoteInvestmentReallocate bool, quoteInvestment, profit, price, priceDeviation fixedpoint.Value, maxOrderCount int64, orderGroupID uint32) ([]types.SubmitOrder, error) {
factor := fixedpoint.One.Sub(priceDeviation)
profit = market.TruncatePrice(profit)
// calculate all valid prices
var prices []fixedpoint.Value
for i := 0; i < int(maxOrderCount); i++ {
if i > 0 {
price = price.Mul(factor)
}
2023-12-07 06:38:13 +00:00
price = market.TruncatePrice(price)
if price.Compare(market.MinPrice) < 0 {
break
}
prices = append(prices, price)
}
notional, orderNum := calculateNotionalAndNumOrders(market, quoteInvestment, prices)
if orderNum == 0 {
return nil, fmt.Errorf("failed to calculate notional and num of open position orders, price: %s, quote investment: %s", price, quoteInvestment)
}
if !enableQuoteInvestmentReallocate && orderNum != int(maxOrderCount) {
return nil, fmt.Errorf("failed to generate open-position orders due to the orders may be under min notional or quantity")
}
2023-12-07 06:38:13 +00:00
side := types.SideTypeBuy
var submitOrders []types.SubmitOrder
for i := 0; i < orderNum; i++ {
var quantity fixedpoint.Value
// all the profit will use in the first order
if i == 0 {
quantity = market.TruncateQuantity(notional.Add(profit).Div(prices[i]))
} else {
quantity = market.TruncateQuantity(notional.Div(prices[i]))
}
submitOrders = append(submitOrders, types.SubmitOrder{
2023-12-07 06:38:13 +00:00
Symbol: market.Symbol,
Market: market,
Type: types.OrderTypeLimit,
Price: prices[i],
2023-12-07 06:38:13 +00:00
Side: side,
TimeInForce: types.TimeInForceGTC,
Quantity: quantity,
Tag: orderTag,
2023-12-07 06:38:13 +00:00
GroupID: orderGroupID,
})
}
return submitOrders, nil
}
// calculateNotionalAndNumOrders calculates the notional and num of open position orders
// DCA2 is notional-based, every order has the same notional
func calculateNotionalAndNumOrders(market types.Market, quoteInvestment fixedpoint.Value, prices []fixedpoint.Value) (fixedpoint.Value, int) {
for num := len(prices); num > 0; num-- {
notional := quoteInvestment.Div(fixedpoint.NewFromInt(int64(num)))
if notional.Compare(market.MinNotional) < 0 {
continue
}
maxPriceIdx := 0
quantity := market.TruncateQuantity(notional.Div(prices[maxPriceIdx]))
if quantity.Compare(market.MinQuantity) < 0 {
continue
}
2024-01-17 07:21:27 +00:00
return market.TruncatePrice(notional), num
}
return fixedpoint.Zero, 0
}