mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 23:05:15 +00:00
fix position and add catchup mode for grid strategy
This commit is contained in:
parent
60aa7df69a
commit
f56df038aa
|
@ -57,24 +57,37 @@ type Strategy struct {
|
|||
|
||||
// Quantity is the quantity you want to submit for each order.
|
||||
Quantity fixedpoint.Value `json:"quantity,omitempty"`
|
||||
|
||||
// ScaleQuantity helps user to define the quantity by price scale or volume scale
|
||||
ScaleQuantity *bbgo.PriceVolumeScale `json:"scaleQuantity,omitempty"`
|
||||
|
||||
// FixedAmount is used for fixed amount (dynamic quantity) if you don't want to use fixed quantity.
|
||||
FixedAmount fixedpoint.Value `json:"amount,omitempty" yaml:"amount"`
|
||||
|
||||
// Side is the initial maker orders side. defaults to "both"
|
||||
Side types.SideType `json:"side" yaml:"side"`
|
||||
|
||||
// CatchUp let the maker grid catch up with the price change.
|
||||
CatchUp bool `json:"catchUp" yaml:"catchUp"`
|
||||
|
||||
// Long means you want to hold more base asset than the quote asset.
|
||||
Long bool `json:"long,omitempty" yaml:"long,omitempty"`
|
||||
|
||||
filledBuyGrids map[fixedpoint.Value]struct{}
|
||||
filledSellGrids map[fixedpoint.Value]struct{}
|
||||
|
||||
// orderStore is used to store all the created orders, so that we can filter the trades.
|
||||
orderStore *bbgo.OrderStore
|
||||
|
||||
// activeOrders is the locally maintained active order book of the maker orders.
|
||||
activeOrders *bbgo.LocalActiveOrderBook
|
||||
|
||||
position fixedpoint.Value
|
||||
position bbgo.Position
|
||||
|
||||
// any created orders for tracking trades
|
||||
orders map[uint64]types.Order
|
||||
|
||||
// groupID is the group ID used for the strategy instance for canceling orders
|
||||
groupID int64
|
||||
}
|
||||
|
||||
|
@ -141,6 +154,11 @@ func (s *Strategy) generateGridSellOrders(session *bbgo.ExchangeSession) ([]type
|
|||
baseBalance.Available.Float64())
|
||||
}
|
||||
|
||||
if _, filled := s.filledSellGrids[price]; filled {
|
||||
log.Infof("sell grid at price %f is already filled, skipping", price.Float64())
|
||||
continue
|
||||
}
|
||||
|
||||
orders = append(orders, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
|
@ -152,12 +170,15 @@ func (s *Strategy) generateGridSellOrders(session *bbgo.ExchangeSession) ([]type
|
|||
GroupID: s.groupID,
|
||||
})
|
||||
baseBalance.Available -= quantity
|
||||
|
||||
s.filledSellGrids[price] = struct{}{}
|
||||
}
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
func (s *Strategy) generateGridBuyOrders(session *bbgo.ExchangeSession) ([]types.SubmitOrder, error) {
|
||||
// session.Exchange.QueryTicker()
|
||||
currentPriceFloat, ok := session.LastPrice(s.Symbol)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s last price not found, skipping", s.Symbol)
|
||||
|
@ -217,6 +238,11 @@ func (s *Strategy) generateGridBuyOrders(session *bbgo.ExchangeSession) ([]types
|
|||
quoteQuantity.Float64())
|
||||
}
|
||||
|
||||
if _, filled := s.filledBuyGrids[price]; filled {
|
||||
log.Infof("buy grid at price %f is already filled, skipping", price.Float64())
|
||||
continue
|
||||
}
|
||||
|
||||
orders = append(orders, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeBuy,
|
||||
|
@ -228,40 +254,74 @@ func (s *Strategy) generateGridBuyOrders(session *bbgo.ExchangeSession) ([]types
|
|||
GroupID: s.groupID,
|
||||
})
|
||||
balance.Available -= quoteQuantity
|
||||
|
||||
s.filledBuyGrids[price] = struct{}{}
|
||||
}
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
func (s *Strategy) placeGridSellOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
orderForms, err := s.generateGridSellOrders(session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(orderForms) > 0 {
|
||||
createdOrders, err := orderExecutor.SubmitOrders(context.Background(), orderForms...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.activeOrders.Add(createdOrders...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) placeGridBuyOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
orderForms, err := s.generateGridBuyOrders(session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(orderForms) > 0 {
|
||||
createdOrders, err := orderExecutor.SubmitOrders(context.Background(), orderForms...)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
s.activeOrders.Add(createdOrders...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) placeGridOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) {
|
||||
log.Infof("placing grid orders...")
|
||||
|
||||
sellOrders, err := s.generateGridSellOrders(session)
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
if len(sellOrders) > 0 {
|
||||
createdSellOrders, err := orderExecutor.SubmitOrders(context.Background(), sellOrders...)
|
||||
if err != nil {
|
||||
log.WithError(err).Error(err.Error())
|
||||
} else {
|
||||
s.activeOrders.Add(createdSellOrders...)
|
||||
}
|
||||
}
|
||||
switch s.Side {
|
||||
|
||||
buyOrders, err := s.generateGridBuyOrders(session)
|
||||
if err != nil {
|
||||
case types.SideTypeBuy:
|
||||
if err := s.placeGridBuyOrders(orderExecutor, session); err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
|
||||
if len(buyOrders) > 0 {
|
||||
createdBuyOrders, err := orderExecutor.SubmitOrders(context.Background(), buyOrders...)
|
||||
if err != nil {
|
||||
log.WithError(err).Error(err.Error())
|
||||
} else {
|
||||
s.activeOrders.Add(createdBuyOrders...)
|
||||
case types.SideTypeSell:
|
||||
if err := s.placeGridSellOrders(orderExecutor, session); err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
|
||||
case types.SideTypeBoth:
|
||||
if err := s.placeGridSellOrders(orderExecutor, session); err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
|
||||
if err := s.placeGridBuyOrders(orderExecutor, session); err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *Strategy) tradeUpdateHandler(trade types.Trade) {
|
||||
|
@ -271,11 +331,14 @@ func (s *Strategy) tradeUpdateHandler(trade types.Trade) {
|
|||
|
||||
if s.orderStore.Exists(trade.OrderID) {
|
||||
log.Infof("received trade update of order %d: %+v", trade.OrderID, trade)
|
||||
switch trade.Side {
|
||||
case types.SideTypeBuy:
|
||||
s.position.AtomicAdd(fixedpoint.NewFromFloat(trade.Quantity))
|
||||
case types.SideTypeSell:
|
||||
s.position.AtomicAdd(-fixedpoint.NewFromFloat(trade.Quantity))
|
||||
|
||||
if trade.Side == types.SideTypeSelf {
|
||||
return
|
||||
}
|
||||
|
||||
profit, madeProfit := s.position.AddTrade(trade)
|
||||
if madeProfit {
|
||||
s.Notify("profit: %f", profit.Float64())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -333,10 +396,17 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.GridNum = 10
|
||||
}
|
||||
|
||||
if s.Side == "" {
|
||||
s.Side = types.SideTypeBoth
|
||||
}
|
||||
|
||||
if s.UpperPrice <= s.LowerPrice {
|
||||
return fmt.Errorf("upper price (%f) should not be less than lower price (%f)", s.UpperPrice.Float64(), s.LowerPrice.Float64())
|
||||
}
|
||||
|
||||
s.filledBuyGrids = make(map[fixedpoint.Value]struct{})
|
||||
s.filledSellGrids = make(map[fixedpoint.Value]struct{})
|
||||
|
||||
position, ok := session.Position(s.Symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("position not found")
|
||||
|
@ -344,6 +414,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
log.Infof("position: %+v", position)
|
||||
|
||||
s.position = bbgo.Position{
|
||||
Symbol: s.Symbol,
|
||||
BaseCurrency: s.Market.BaseCurrency,
|
||||
QuoteCurrency: s.Market.QuoteCurrency,
|
||||
}
|
||||
|
||||
instanceID := fmt.Sprintf("grid-%s-%d", s.Symbol, s.GridNum)
|
||||
s.groupID = generateGroupID(instanceID)
|
||||
log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
|
||||
|
@ -360,12 +436,19 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
defer wg.Done()
|
||||
|
||||
log.Infof("canceling active orders...")
|
||||
|
||||
if err := session.Exchange.CancelOrders(ctx, s.activeOrders.Orders()...); err != nil {
|
||||
log.WithError(err).Errorf("cancel order error")
|
||||
}
|
||||
})
|
||||
|
||||
if s.CatchUp {
|
||||
session.Stream.OnKLineClosed(func(kline types.KLine) {
|
||||
log.Infof("catchUp mode is enabled, updating grid orders...")
|
||||
// update grid
|
||||
s.placeGridOrders(orderExecutor, session)
|
||||
})
|
||||
}
|
||||
|
||||
session.Stream.OnTradeUpdate(s.tradeUpdateHandler)
|
||||
session.Stream.OnStart(func() {
|
||||
s.placeGridOrders(orderExecutor, session)
|
||||
|
|
Loading…
Reference in New Issue
Block a user