mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 23:05:15 +00:00
Merge pull request #1436 from c9s/kbearXD/dca2/open-takeprofit-orders
FEATURE: cancel maker orders and open take profit order
This commit is contained in:
commit
7e61fe40eb
|
@ -9,6 +9,10 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"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 {
|
func (s *Strategy) placeOpenPositionOrders(ctx context.Context) error {
|
||||||
s.logger.Infof("[DCA] start placing open position orders")
|
s.logger.Infof("[DCA] start placing open position orders")
|
||||||
price, err := getBestPriceUntilSuccess(ctx, s.Session.Exchange, s.Symbol, s.Short)
|
price, err := getBestPriceUntilSuccess(ctx, s.Session.Exchange, s.Symbol, s.Short)
|
||||||
|
@ -16,7 +20,7 @@ func (s *Strategy) placeOpenPositionOrders(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
orders, err := s.generateOpenPositionOrders(s.Short, s.Budget, price, s.PriceDeviation, s.MaxOrderNum)
|
orders, err := generateOpenPositionOrders(s.Market, s.Short, s.Budget, price, s.PriceDeviation, s.MaxOrderNum, s.OrderGroupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -44,7 +48,7 @@ func getBestPriceUntilSuccess(ctx context.Context, ex types.Exchange, symbol str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) generateOpenPositionOrders(short bool, budget, price, priceDeviation fixedpoint.Value, maxOrderNum int64) ([]types.SubmitOrder, error) {
|
func generateOpenPositionOrders(market types.Market, short bool, budget, price, priceDeviation fixedpoint.Value, maxOrderNum int64, orderGroupID uint32) ([]types.SubmitOrder, error) {
|
||||||
factor := fixedpoint.One.Sub(priceDeviation)
|
factor := fixedpoint.One.Sub(priceDeviation)
|
||||||
if short {
|
if short {
|
||||||
factor = fixedpoint.One.Add(priceDeviation)
|
factor = fixedpoint.One.Add(priceDeviation)
|
||||||
|
@ -56,32 +60,37 @@ func (s *Strategy) generateOpenPositionOrders(short bool, budget, price, priceDe
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
price = price.Mul(factor)
|
price = price.Mul(factor)
|
||||||
}
|
}
|
||||||
price = s.Market.TruncatePrice(price)
|
price = market.TruncatePrice(price)
|
||||||
if price.Compare(s.Market.MinPrice) < 0 {
|
if price.Compare(market.MinPrice) < 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
prices = append(prices, price)
|
prices = append(prices, price)
|
||||||
}
|
}
|
||||||
|
|
||||||
notional, orderNum := calculateNotionalAndNum(s.Market, short, budget, prices)
|
notional, orderNum := calculateNotionalAndNum(market, short, budget, prices)
|
||||||
if orderNum == 0 {
|
if orderNum == 0 {
|
||||||
return nil, fmt.Errorf("failed to calculate notional and num of open position orders, price: %s, budget: %s", price, budget)
|
return nil, fmt.Errorf("failed to calculate notional and num of open position orders, price: %s, budget: %s", price, budget)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
side := types.SideTypeBuy
|
||||||
|
if short {
|
||||||
|
side = types.SideTypeSell
|
||||||
|
}
|
||||||
|
|
||||||
var submitOrders []types.SubmitOrder
|
var submitOrders []types.SubmitOrder
|
||||||
for i := 0; i < orderNum; i++ {
|
for i := 0; i < orderNum; i++ {
|
||||||
quantity := s.Market.TruncateQuantity(notional.Div(prices[i]))
|
quantity := market.TruncateQuantity(notional.Div(prices[i]))
|
||||||
submitOrders = append(submitOrders, types.SubmitOrder{
|
submitOrders = append(submitOrders, types.SubmitOrder{
|
||||||
Symbol: s.Symbol,
|
Symbol: market.Symbol,
|
||||||
Market: s.Market,
|
Market: market,
|
||||||
Type: types.OrderTypeLimit,
|
Type: types.OrderTypeLimit,
|
||||||
Price: prices[i],
|
Price: prices[i],
|
||||||
Side: s.makerSide,
|
Side: side,
|
||||||
TimeInForce: types.TimeInForceGTC,
|
TimeInForce: types.TimeInForceGTC,
|
||||||
Quantity: quantity,
|
Quantity: quantity,
|
||||||
Tag: orderTag,
|
Tag: orderTag,
|
||||||
GroupID: s.OrderGroupID,
|
GroupID: orderGroupID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,3 +120,24 @@ func calculateNotionalAndNum(market types.Market, short bool, budget fixedpoint.
|
||||||
|
|
||||||
return fixedpoint.Zero, 0
|
return fixedpoint.Zero, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) cancelOpenPositionOrders(ctx context.Context) error {
|
||||||
|
s.logger.Info("[DCA] cancel open position orders")
|
||||||
|
e, ok := s.Session.Exchange.(cancelOrdersByGroupIDApi)
|
||||||
|
if ok {
|
||||||
|
cancelledOrders, err := e.CancelOrdersByGroupID(ctx, int64(s.OrderGroupID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cancelledOrder := range cancelledOrders {
|
||||||
|
s.logger.Info("CANCEL ", cancelledOrder.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := s.OrderExecutor.ActiveMakerOrders().GracefulCancel(ctx, s.Session.Exchange); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
func newTestMarket() types.Market {
|
func newTestMarket() types.Market {
|
||||||
return types.Market{
|
return types.Market{
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
BaseCurrency: "BTC",
|
BaseCurrency: "BTC",
|
||||||
QuoteCurrency: "USDT",
|
QuoteCurrency: "USDT",
|
||||||
TickSize: Number(0.01),
|
TickSize: Number(0.01),
|
||||||
|
@ -32,9 +33,13 @@ func newTestStrategy(va ...string) *Strategy {
|
||||||
|
|
||||||
market := newTestMarket()
|
market := newTestMarket()
|
||||||
s := &Strategy{
|
s := &Strategy{
|
||||||
logger: logrus.NewEntry(logrus.New()),
|
logger: logrus.NewEntry(logrus.New()),
|
||||||
Symbol: symbol,
|
Symbol: symbol,
|
||||||
Market: market,
|
Market: market,
|
||||||
|
Short: false,
|
||||||
|
TakeProfitRatio: Number("10%"),
|
||||||
|
openPositionSide: types.SideTypeBuy,
|
||||||
|
takeProfitSide: types.SideTypeSell,
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -48,7 +53,7 @@ func TestGenerateOpenPositionOrders(t *testing.T) {
|
||||||
budget := Number("10500")
|
budget := Number("10500")
|
||||||
askPrice := Number("30000")
|
askPrice := Number("30000")
|
||||||
margin := Number("0.05")
|
margin := Number("0.05")
|
||||||
submitOrders, err := strategy.generateOpenPositionOrders(false, budget, askPrice, margin, 4)
|
submitOrders, err := generateOpenPositionOrders(strategy.Market, false, budget, askPrice, margin, 4, strategy.OrderGroupID)
|
||||||
if !assert.NoError(err) {
|
if !assert.NoError(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -50,7 +50,7 @@ type Strategy struct {
|
||||||
|
|
||||||
// private field
|
// private field
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
makerSide types.SideType
|
openPositionSide types.SideType
|
||||||
takeProfitSide types.SideType
|
takeProfitSide types.SideType
|
||||||
takeProfitPrice fixedpoint.Value
|
takeProfitPrice fixedpoint.Value
|
||||||
startTimeOfNextRound time.Time
|
startTimeOfNextRound time.Time
|
||||||
|
@ -106,10 +106,10 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
||||||
instanceID := s.InstanceID()
|
instanceID := s.InstanceID()
|
||||||
|
|
||||||
if s.Short {
|
if s.Short {
|
||||||
s.makerSide = types.SideTypeSell
|
s.openPositionSide = types.SideTypeSell
|
||||||
s.takeProfitSide = types.SideTypeBuy
|
s.takeProfitSide = types.SideTypeBuy
|
||||||
} else {
|
} else {
|
||||||
s.makerSide = types.SideTypeBuy
|
s.openPositionSide = types.SideTypeBuy
|
||||||
s.takeProfitSide = types.SideTypeSell
|
s.takeProfitSide = types.SideTypeSell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
43
pkg/strategy/dca2/take_profit.go
Normal file
43
pkg/strategy/dca2/take_profit.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package dca2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Strategy) placeTakeProfitOrders(ctx context.Context) error {
|
||||||
|
s.logger.Info("[DCA] start placing take profit orders")
|
||||||
|
order := generateTakeProfitOrder(s.Market, s.Short, s.TakeProfitRatio, s.Position, s.OrderGroupID)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTakeProfitOrder(market types.Market, short bool, takeProfitRatio fixedpoint.Value, position *types.Position, orderGroupID uint32) types.SubmitOrder {
|
||||||
|
side := types.SideTypeSell
|
||||||
|
if short {
|
||||||
|
takeProfitRatio = takeProfitRatio.Neg()
|
||||||
|
side = types.SideTypeBuy
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
47
pkg/strategy/dca2/take_profit_test.go
Normal file
47
pkg/strategy/dca2/take_profit_test.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package dca2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/c9s/bbgo/pkg/testing/testhelper"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateTakeProfitOrder(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
strategy := newTestStrategy()
|
||||||
|
|
||||||
|
position := types.NewPositionFromMarket(strategy.Market)
|
||||||
|
position.AddTrade(types.Trade{
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Price: Number("28500"),
|
||||||
|
Quantity: Number("1"),
|
||||||
|
QuoteQuantity: Number("28500"),
|
||||||
|
Fee: Number("0.0015"),
|
||||||
|
FeeCurrency: strategy.Market.BaseCurrency,
|
||||||
|
})
|
||||||
|
|
||||||
|
o := generateTakeProfitOrder(strategy.Market, false, strategy.TakeProfitRatio, position, strategy.OrderGroupID)
|
||||||
|
assert.Equal(Number("31397.09"), o.Price)
|
||||||
|
assert.Equal(Number("0.9985"), o.Quantity)
|
||||||
|
assert.Equal(types.SideTypeSell, o.Side)
|
||||||
|
assert.Equal(strategy.Symbol, o.Symbol)
|
||||||
|
|
||||||
|
position.AddTrade(types.Trade{
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Price: Number("27000"),
|
||||||
|
Quantity: Number("0.5"),
|
||||||
|
QuoteQuantity: Number("13500"),
|
||||||
|
Fee: Number("0.00075"),
|
||||||
|
FeeCurrency: strategy.Market.BaseCurrency,
|
||||||
|
})
|
||||||
|
o = generateTakeProfitOrder(strategy.Market, false, strategy.TakeProfitRatio, position, strategy.OrderGroupID)
|
||||||
|
assert.Equal(Number("30846.26"), o.Price)
|
||||||
|
assert.Equal(Number("1.49775"), o.Quantity)
|
||||||
|
assert.Equal(types.SideTypeSell, o.Side)
|
||||||
|
assert.Equal(strategy.Symbol, o.Symbol)
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user