bbgo_origin/pkg/strategy/grid/strategy.go

651 lines
20 KiB
Go
Raw Normal View History

package grid
import (
"context"
2020-12-31 05:54:32 +00:00
"fmt"
2021-12-05 17:34:08 +00:00
"sync"
2021-03-16 10:39:18 +00:00
"github.com/pkg/errors"
2020-10-29 13:10:13 +00:00
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
2021-03-22 09:27:07 +00:00
"github.com/c9s/bbgo/pkg/exchange/max"
"github.com/c9s/bbgo/pkg/fixedpoint"
2021-02-16 08:30:01 +00:00
"github.com/c9s/bbgo/pkg/service"
"github.com/c9s/bbgo/pkg/types"
)
2021-02-03 01:08:05 +00:00
const ID = "grid"
var log = logrus.WithField("strategy", ID)
2020-10-29 13:10:13 +00:00
func init() {
// Register the pointer of the strategy struct,
// so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON)
// Note: built-in strategies need to imported manually in the bbgo cmd package.
2021-02-03 01:08:05 +00:00
bbgo.RegisterStrategy(ID, &Strategy{})
}
// State is the grid snapshot
type State struct {
2021-03-16 10:39:18 +00:00
Orders []types.SubmitOrder `json:"orders,omitempty"`
FilledBuyGrids map[fixedpoint.Value]struct{} `json:"filledBuyGrids"`
FilledSellGrids map[fixedpoint.Value]struct{} `json:"filledSellGrids"`
Position *types.Position `json:"position,omitempty"`
2021-03-18 09:20:21 +00:00
AccumulativeArbitrageProfit fixedpoint.Value `json:"accumulativeArbitrageProfit"`
2021-03-18 09:20:21 +00:00
// any created orders for tracking trades
// [source Order ID] -> arbitrage order
ArbitrageOrders map[uint64]types.Order `json:"arbitrageOrders"`
ProfitStats bbgo.ProfitStats `json:"profitStats,omitempty"`
2021-03-16 10:39:18 +00:00
}
type Strategy struct {
// The notification system will be injected into the strategy automatically.
// This field will be injected automatically since it's a single exchange strategy.
2021-02-02 18:26:41 +00:00
*bbgo.Notifiability `json:"-" yaml:"-"`
2021-02-02 18:26:41 +00:00
*bbgo.Graceful `json:"-" yaml:"-"`
2020-11-12 06:50:08 +00:00
2021-03-16 10:39:18 +00:00
*bbgo.Persistence
// OrderExecutor is an interface for submitting order.
// This field will be injected automatically since it's a single exchange strategy.
2021-02-02 18:26:41 +00:00
bbgo.OrderExecutor `json:"-" yaml:"-"`
// Market stores the configuration of the market, for example, VolumePrecision, PricePrecision, MinLotSize... etc
// This field will be injected automatically since we defined the Symbol field.
2021-02-02 18:26:41 +00:00
types.Market `json:"-" yaml:"-"`
2021-02-16 08:30:01 +00:00
TradeService *service.TradeService `json:"-" yaml:"-"`
// These fields will be filled from the config file (it translates YAML to JSON)
2021-02-02 18:26:41 +00:00
Symbol string `json:"symbol" yaml:"symbol"`
2020-11-10 11:06:20 +00:00
// ProfitSpread is the fixed profit spread you want to submit the sell order
2021-02-02 18:26:41 +00:00
ProfitSpread fixedpoint.Value `json:"profitSpread" yaml:"profitSpread"`
2020-11-05 07:04:56 +00:00
// GridNum is the grid number, how many orders you want to post on the orderbook.
2021-02-02 18:26:41 +00:00
GridNum int `json:"gridNumber" yaml:"gridNumber"`
2021-02-02 18:26:41 +00:00
UpperPrice fixedpoint.Value `json:"upperPrice" yaml:"upperPrice"`
2020-11-10 11:06:20 +00:00
2021-02-02 18:26:41 +00:00
LowerPrice fixedpoint.Value `json:"lowerPrice" yaml:"lowerPrice"`
2020-11-10 11:06:20 +00:00
// Quantity is the quantity you want to submit for each order.
Quantity fixedpoint.Value `json:"quantity,omitempty"`
2021-05-09 18:52:41 +00:00
// QuantityScale helps user to define the quantity by price scale or volume scale
QuantityScale *bbgo.PriceVolumeScale `json:"quantityScale,omitempty"`
2021-02-02 18:26:41 +00:00
// 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"`
2021-01-06 05:31:17 +00:00
// 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"`
2021-01-06 05:31:17 +00:00
// Long means you want to hold more base asset than the quote asset.
2021-02-02 18:26:41 +00:00
Long bool `json:"long,omitempty" yaml:"long,omitempty"`
state *State
// orderStore is used to store all the created orders, so that we can filter the trades.
2021-02-02 18:26:41 +00:00
orderStore *bbgo.OrderStore
2020-12-31 05:54:32 +00:00
// activeOrders is the locally maintained active order book of the maker orders.
2020-11-05 06:27:22 +00:00
activeOrders *bbgo.LocalActiveOrderBook
tradeCollector *bbgo.TradeCollector
// groupID is the group ID used for the strategy instance for canceling orders
2021-03-22 09:27:07 +00:00
groupID uint32
}
2021-02-03 01:08:05 +00:00
func (s *Strategy) ID() string {
return ID
}
2021-04-02 02:27:20 +00:00
func (s *Strategy) Validate() error {
if s.UpperPrice == 0 {
return errors.New("upperPrice can not be zero, you forgot to set?")
}
if s.LowerPrice == 0 {
return errors.New("lowerPrice can not be zero, you forgot to set?")
}
if s.UpperPrice <= s.LowerPrice {
return fmt.Errorf("upperPrice (%f) should not be less than or equal to lowerPrice (%f)", s.UpperPrice.Float64(), s.LowerPrice.Float64())
}
if s.ProfitSpread <= 0 {
// If profitSpread is empty or its value is negative
return fmt.Errorf("profit spread should bigger than 0")
}
if s.Quantity == 0 && s.QuantityScale == nil && s.FixedAmount == 0 {
return fmt.Errorf("amount, quantity or scaleQuantity can not be zero")
2021-04-02 02:27:20 +00:00
}
return nil
}
func (s *Strategy) generateGridSellOrders(session *bbgo.ExchangeSession) ([]types.SubmitOrder, error) {
currentPriceFloat, ok := session.LastPrice(s.Symbol)
if !ok {
2021-05-07 16:59:30 +00:00
return nil, fmt.Errorf("can not generate sell orders, %s last price not found", s.Symbol)
}
currentPrice := fixedpoint.NewFromFloat(currentPriceFloat)
2021-03-17 16:46:25 +00:00
if currentPrice > s.UpperPrice {
2021-05-07 16:59:30 +00:00
return nil, fmt.Errorf("can not generate sell orders, the current price %f is higher than upper price %f", currentPrice.Float64(), s.UpperPrice.Float64())
}
2021-03-17 16:46:25 +00:00
priceRange := s.UpperPrice - s.LowerPrice
numGrids := fixedpoint.NewFromInt(s.GridNum)
gridSpread := priceRange.Div(numGrids)
2021-03-17 16:46:25 +00:00
// find the nearest grid price from the current price
startPrice := fixedpoint.Max(
s.LowerPrice,
s.UpperPrice-(s.UpperPrice-currentPrice).Div(gridSpread).Floor().Mul(gridSpread))
if startPrice > s.UpperPrice {
return nil, fmt.Errorf("current price %f exceeded the upper price boundary %f",
currentPrice.Float64(),
s.UpperPrice.Float64())
}
2020-12-29 10:18:32 +00:00
balances := session.Account.Balances()
baseBalance, ok := balances[s.Market.BaseCurrency]
if !ok {
return nil, fmt.Errorf("base balance %s not found", s.Market.BaseCurrency)
}
if baseBalance.Available == 0 {
return nil, fmt.Errorf("base balance %s is zero: %+v", s.Market.BaseCurrency, baseBalance)
}
log.Infof("placing grid sell orders from %f ~ %f, grid spread %f",
2021-02-08 05:21:22 +00:00
startPrice.Float64(),
s.UpperPrice.Float64(),
gridSpread.Float64())
var orders []types.SubmitOrder
for price := startPrice; price <= s.UpperPrice; price += gridSpread {
var quantity fixedpoint.Value
if s.Quantity > 0 {
quantity = s.Quantity
2021-05-09 18:52:41 +00:00
} else if s.QuantityScale != nil {
qf, err := s.QuantityScale.Scale(price.Float64(), 0)
if err != nil {
return nil, err
}
quantity = fixedpoint.NewFromFloat(qf)
} else if s.FixedAmount > 0 {
quantity = s.FixedAmount.Div(price)
}
// quoteQuantity := price.Mul(quantity)
if baseBalance.Available < quantity {
return orders, fmt.Errorf("base balance %s %f is not enough, stop generating sell orders",
baseBalance.Currency,
baseBalance.Available.Float64())
}
if _, filled := s.state.FilledSellGrids[price]; filled {
2021-03-15 18:21:46 +00:00
log.Debugf("sell grid at price %f is already filled, skipping", price.Float64())
continue
}
orders = append(orders, types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeLimit,
Market: s.Market,
Quantity: quantity.Float64(),
Price: price.Float64() + s.ProfitSpread.Float64(),
TimeInForce: "GTC",
GroupID: s.groupID,
})
baseBalance.Available -= quantity
s.state.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)
2020-11-10 11:06:20 +00:00
if !ok {
return nil, fmt.Errorf("%s last price not found, skipping", s.Symbol)
}
currentPrice := fixedpoint.NewFromFloat(currentPriceFloat)
2021-03-17 16:46:25 +00:00
if currentPrice < s.LowerPrice {
return nil, fmt.Errorf("current price %f is lower than the lower price %f", currentPrice.Float64(), s.LowerPrice.Float64())
}
2021-03-17 16:46:25 +00:00
priceRange := s.UpperPrice - s.LowerPrice
numGrids := fixedpoint.NewFromInt(s.GridNum)
gridSpread := priceRange.Div(numGrids)
2021-03-17 16:46:25 +00:00
// Find the nearest grid price for placing buy orders:
// buyRange = currentPrice - lowerPrice
// numOfBuyGrids = Floor(buyRange / gridSpread)
// startPrice = lowerPrice + numOfBuyGrids * gridSpread
// priceOfBuyOrder1 = startPrice
// priceOfBuyOrder2 = startPrice - gridSpread
// priceOfBuyOrder3 = startPrice - gridSpread * 2
startPrice := fixedpoint.Min(
s.UpperPrice,
s.LowerPrice+(currentPrice-s.LowerPrice).Div(gridSpread).Floor().Mul(gridSpread))
2020-12-29 10:18:32 +00:00
if startPrice < s.LowerPrice {
return nil, fmt.Errorf("current price %f exceeded the lower price boundary %f",
currentPrice.Float64(),
s.UpperPrice.Float64())
}
balances := session.Account.Balances()
balance, ok := balances[s.Market.QuoteCurrency]
if !ok {
return nil, fmt.Errorf("quote balance %s not found", s.Market.QuoteCurrency)
}
if balance.Available == 0 {
return nil, fmt.Errorf("quote balance %s is zero: %+v", s.Market.QuoteCurrency, balance)
}
log.Infof("placing grid buy orders from %f to %f, grid spread %f",
startPrice.Float64(),
s.LowerPrice.Float64(),
gridSpread.Float64())
var orders []types.SubmitOrder
for price := startPrice; s.LowerPrice <= price; price -= gridSpread {
var quantity fixedpoint.Value
if s.Quantity > 0 {
quantity = s.Quantity
2021-05-09 18:52:41 +00:00
} else if s.QuantityScale != nil {
qf, err := s.QuantityScale.Scale(price.Float64(), 0)
if err != nil {
return nil, err
}
quantity = fixedpoint.NewFromFloat(qf)
} else if s.FixedAmount > 0 {
quantity = s.FixedAmount.Div(price)
}
quoteQuantity := price.Mul(quantity)
if balance.Available < quoteQuantity {
return orders, fmt.Errorf("quote balance %s %f is not enough for %f, stop generating buy orders",
balance.Currency,
balance.Available.Float64(),
quoteQuantity.Float64())
2020-11-10 11:06:20 +00:00
}
if _, filled := s.state.FilledBuyGrids[price]; filled {
2021-03-15 18:21:46 +00:00
log.Debugf("buy grid at price %f is already filled, skipping", price.Float64())
continue
}
orders = append(orders, types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeLimit,
Market: s.Market,
Quantity: quantity.Float64(),
Price: price.Float64(),
TimeInForce: "GTC",
GroupID: s.groupID,
})
balance.Available -= quoteQuantity
s.state.FilledBuyGrids[price] = struct{}{}
}
return orders, nil
}
func (s *Strategy) placeGridSellOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
orderForms, err := s.generateGridSellOrders(session)
if len(orderForms) == 0 {
2021-05-07 17:00:57 +00:00
if err != nil {
return err
}
return errors.New("none of sell order is generated")
}
log.Infof("submitting %d sell orders...", len(orderForms))
createdOrders, err := orderExecutor.SubmitOrders(context.Background(), orderForms...)
s.activeOrders.Add(createdOrders...)
return err
}
func (s *Strategy) placeGridBuyOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
orderForms, err := s.generateGridBuyOrders(session)
if len(orderForms) == 0 {
2021-05-07 17:00:57 +00:00
if err != nil {
return err
}
return errors.New("none of buy order is generated")
}
log.Infof("submitting %d buy orders...", len(orderForms))
createdOrders, err := orderExecutor.SubmitOrders(context.Background(), orderForms...)
s.activeOrders.Add(createdOrders...)
return err
}
func (s *Strategy) placeGridOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) {
log.Infof("placing grid orders on side %s...", s.Side)
switch s.Side {
case types.SideTypeBuy:
if err := s.placeGridBuyOrders(orderExecutor, session); err != nil {
log.Warn(err.Error())
}
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())
}
default:
log.Errorf("invalid side %s", s.Side)
}
}
2021-03-18 09:20:21 +00:00
func (s *Strategy) handleFilledOrder(filledOrder types.Order) {
// generate arbitrage order
var side = filledOrder.Side.Reverse()
var price = filledOrder.Price
var quantity = filledOrder.Quantity
var amount = filledOrder.Price * filledOrder.Quantity
2020-12-31 06:29:23 +00:00
2020-11-10 11:06:20 +00:00
switch side {
case types.SideTypeSell:
price += s.ProfitSpread.Float64()
case types.SideTypeBuy:
price -= s.ProfitSpread.Float64()
2020-11-05 07:04:56 +00:00
}
2021-02-02 18:26:41 +00:00
if s.FixedAmount > 0 {
quantity = s.FixedAmount.Float64() / price
2021-01-06 05:31:17 +00:00
} else if s.Long {
// long = use the same amount to buy more quantity back
2020-12-31 05:54:32 +00:00
quantity = amount / price
amount = quantity * price
}
if quantity < s.Market.MinQuantity {
quantity = s.Market.MinQuantity
amount = quantity * price
}
if amount <= s.Market.MinNotional {
quantity = bbgo.AdjustFloatQuantityByMinAmount(quantity, price, s.Market.MinNotional*1.001)
// update amount
amount = quantity * price
2020-12-31 05:54:32 +00:00
}
2020-11-10 11:06:20 +00:00
submitOrder := types.SubmitOrder{
Symbol: s.Symbol,
Side: side,
Type: types.OrderTypeLimit,
2020-12-31 05:54:32 +00:00
Quantity: quantity,
2020-11-10 11:06:20 +00:00
Price: price,
TimeInForce: "GTC",
GroupID: s.groupID,
}
2021-03-18 09:20:21 +00:00
log.Infof("submitting arbitrage order: %s against filled order %s", submitOrder.String(), filledOrder.String())
2020-11-05 07:04:56 +00:00
2020-11-10 11:06:20 +00:00
createdOrders, err := s.OrderExecutor.SubmitOrders(context.Background(), submitOrder)
2020-11-10 06:18:54 +00:00
2021-03-18 09:20:21 +00:00
// create one-way link from the newly created orders
for _, o := range createdOrders {
s.state.ArbitrageOrders[o.OrderID] = filledOrder
}
2020-12-17 08:22:43 +00:00
s.orderStore.Add(createdOrders...)
2020-11-10 11:06:20 +00:00
s.activeOrders.Add(createdOrders...)
2021-03-18 09:20:21 +00:00
if err != nil {
log.WithError(err).Errorf("can not place orders: %+v", submitOrder)
return
}
2021-03-18 09:20:21 +00:00
// calculate arbitrage profit
2021-03-18 09:48:05 +00:00
// TODO: apply fee rate here
2021-03-18 09:20:21 +00:00
if s.Long {
switch filledOrder.Side {
case types.SideTypeSell:
if buyOrder, ok := s.state.ArbitrageOrders[filledOrder.OrderID]; ok {
// use base asset quantity here
baseProfit := buyOrder.Quantity - filledOrder.Quantity
s.state.AccumulativeArbitrageProfit += fixedpoint.NewFromFloat(baseProfit)
2021-11-04 05:08:38 +00:00
s.Notify("%s grid arbitrage profit %f %s, accumulative arbitrage profit %f %s",
s.Symbol,
baseProfit, s.Market.BaseCurrency,
2021-03-25 07:22:52 +00:00
s.state.AccumulativeArbitrageProfit.Float64(), s.Market.BaseCurrency,
)
2021-03-18 09:20:21 +00:00
}
case types.SideTypeBuy:
if sellOrder, ok := s.state.ArbitrageOrders[filledOrder.OrderID]; ok {
// use base asset quantity here
baseProfit := filledOrder.Quantity - sellOrder.Quantity
s.state.AccumulativeArbitrageProfit += fixedpoint.NewFromFloat(baseProfit)
2021-11-04 05:08:38 +00:00
s.Notify("%s grid arbitrage profit %f %s, accumulative arbitrage profit %f %s",
s.Symbol,
baseProfit, s.Market.BaseCurrency,
2021-03-25 07:22:52 +00:00
s.state.AccumulativeArbitrageProfit.Float64(), s.Market.BaseCurrency,
)
2021-03-18 09:20:21 +00:00
}
}
} else if !s.Long && s.Quantity > 0 {
switch filledOrder.Side {
case types.SideTypeSell:
if buyOrder, ok := s.state.ArbitrageOrders[filledOrder.OrderID]; ok {
// use base asset quantity here
quoteProfit := (filledOrder.Quantity * filledOrder.Price) - (buyOrder.Quantity * buyOrder.Price)
s.state.AccumulativeArbitrageProfit += fixedpoint.NewFromFloat(quoteProfit)
2021-11-04 05:08:38 +00:00
s.Notify("%s grid arbitrage profit %f %s, accumulative arbitrage profit %f %s",
s.Symbol,
quoteProfit, s.Market.QuoteCurrency,
2021-03-25 07:22:52 +00:00
s.state.AccumulativeArbitrageProfit.Float64(), s.Market.QuoteCurrency,
)
2021-03-18 09:20:21 +00:00
}
case types.SideTypeBuy:
if sellOrder, ok := s.state.ArbitrageOrders[filledOrder.OrderID]; ok {
// use base asset quantity here
quoteProfit := (sellOrder.Quantity * sellOrder.Price) - (filledOrder.Quantity * filledOrder.Price)
s.state.AccumulativeArbitrageProfit += fixedpoint.NewFromFloat(quoteProfit)
s.Notify("%s grid arbitrage profit %f %s, accumulative arbitrage profit %f %s", s.Symbol,
quoteProfit, s.Market.QuoteCurrency,
2021-03-25 07:22:52 +00:00
s.state.AccumulativeArbitrageProfit.Float64(), s.Market.QuoteCurrency,
)
2021-03-18 09:20:21 +00:00
}
}
}
}
2020-11-10 11:06:20 +00:00
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
}
func (s *Strategy) LoadState() error {
instanceID := s.InstanceID()
2021-03-20 15:07:01 +00:00
var state State
2021-03-16 10:39:18 +00:00
if s.Persistence != nil {
2021-03-20 15:07:01 +00:00
if err := s.Persistence.Load(&state, ID, instanceID); err != nil {
2021-03-16 10:39:18 +00:00
if err != service.ErrPersistenceNotExists {
return errors.Wrapf(err, "state load error")
2021-03-16 10:39:18 +00:00
}
s.state = &State{
FilledBuyGrids: make(map[fixedpoint.Value]struct{}),
FilledSellGrids: make(map[fixedpoint.Value]struct{}),
ArbitrageOrders: make(map[uint64]types.Order),
Position: types.NewPositionFromMarket(s.Market),
}
2021-03-16 10:39:18 +00:00
} else {
s.state = &state
2021-03-16 10:39:18 +00:00
}
}
// init profit stats
s.state.ProfitStats.Init(s.Market)
2021-11-04 17:04:04 +00:00
// field guards
if s.state.ArbitrageOrders == nil {
s.state.ArbitrageOrders = make(map[uint64]types.Order)
}
2021-11-04 17:04:04 +00:00
if s.state.FilledBuyGrids == nil {
s.state.FilledBuyGrids = make(map[fixedpoint.Value]struct{})
}
if s.state.FilledSellGrids == nil {
s.state.FilledSellGrids = make(map[fixedpoint.Value]struct{})
}
return nil
}
func (s *Strategy) SaveState() error {
if s.Persistence != nil {
log.Infof("backing up grid state...")
instanceID := s.InstanceID()
submitOrders := s.activeOrders.Backup()
s.state.Orders = submitOrders
if err := s.Persistence.Save(s.state, ID, instanceID); err != nil {
return err
}
2021-02-16 08:30:01 +00:00
}
return nil
}
2021-02-16 08:30:01 +00:00
2021-11-04 17:04:04 +00:00
// InstanceID returns the instance identifier from the current grid configuration parameters
func (s *Strategy) InstanceID() string {
return fmt.Sprintf("%s-%s-%d-%d-%d", ID, s.Symbol, s.GridNum, s.UpperPrice.Int(), s.LowerPrice.Int())
}
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
// do some basic validation
if s.GridNum == 0 {
s.GridNum = 10
}
if s.Side == "" {
s.Side = types.SideTypeBoth
}
instanceID := s.InstanceID()
s.groupID = max.GenerateGroupID(instanceID)
log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
if err := s.LoadState(); err != nil {
return err
2021-03-18 09:20:21 +00:00
}
2021-11-04 05:08:38 +00:00
s.Notify("grid %s position", s.Symbol, s.state.Position)
2021-01-21 06:51:37 +00:00
s.orderStore = bbgo.NewOrderStore(s.Symbol)
s.orderStore.BindStream(session.UserDataStream)
2020-10-29 13:10:13 +00:00
// we don't persist orders so that we can not clear the previous orders for now. just need time to support this.
s.activeOrders = bbgo.NewLocalActiveOrderBook(s.Symbol)
2021-03-18 09:20:21 +00:00
s.activeOrders.OnFilled(s.handleFilledOrder)
s.activeOrders.BindStream(session.UserDataStream)
2020-10-29 13:10:13 +00:00
s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.state.Position, s.orderStore)
s.tradeCollector.OnTrade(func(trade types.Trade) {
s.Notifiability.Notify(trade)
s.state.ProfitStats.AddTrade(trade)
})
2021-12-05 17:34:08 +00:00
/*
if s.TradeService != nil {
s.tradeCollector.OnTrade(func(trade types.Trade) {
if err := s.TradeService.Mark(ctx, trade.ID, ID); err != nil {
log.WithError(err).Error("trade mark error")
}
})
}
2021-12-05 17:34:08 +00:00
*/
s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.Notifiability.Notify(position)
})
s.tradeCollector.BindStream(session.UserDataStream)
2020-11-12 06:50:08 +00:00
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
2020-11-12 06:59:47 +00:00
defer wg.Done()
if err := s.SaveState(); err != nil {
log.WithError(err).Errorf("can not save state: %+v", s.state)
} else {
s.Notify("%s: %s grid is saved", ID, s.Symbol)
2021-03-16 10:39:18 +00:00
}
// now we can cancel the open orders
2020-11-12 06:50:08 +00:00
log.Infof("canceling active orders...")
if err := session.Exchange.CancelOrders(context.Background(), s.activeOrders.Orders()...); err != nil {
2020-11-12 06:50:08 +00:00
log.WithError(err).Errorf("cancel order error")
}
})
session.UserDataStream.OnStart(func() {
// if we have orders in the state data, we can restore them
if len(s.state.Orders) > 0 {
2021-11-04 17:04:04 +00:00
s.Notifiability.Notify("restoring %s %d grid orders...", s.Symbol, len(s.state.Orders))
createdOrders, err := orderExecutor.SubmitOrders(ctx, s.state.Orders...)
2021-03-16 10:39:18 +00:00
if err != nil {
log.WithError(err).Error("active orders restore error")
}
s.activeOrders.Add(createdOrders...)
s.orderStore.Add(createdOrders...)
} else {
// or place new orders
2021-03-16 10:39:18 +00:00
s.placeGridOrders(orderExecutor, session)
}
2020-11-05 07:04:56 +00:00
})
if s.CatchUp {
2021-05-27 19:13:50 +00:00
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
log.Infof("catchUp mode is enabled, updating grid orders...")
// update grid
s.placeGridOrders(orderExecutor, session)
})
}
return nil
}