2022-11-02 10:26:30 +00:00
|
|
|
package grid2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/bbgo"
|
|
|
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
"github.com/c9s/bbgo/pkg/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
const ID = "grid2"
|
|
|
|
|
|
|
|
var log = logrus.WithField("strategy", ID)
|
|
|
|
|
|
|
|
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.
|
|
|
|
bbgo.RegisterStrategy(ID, &Strategy{})
|
|
|
|
}
|
|
|
|
|
2022-11-14 08:28:07 +00:00
|
|
|
type GridProfitStats struct {
|
|
|
|
TotalProfit fixedpoint.Value `json:"totalProfit"`
|
|
|
|
FloatProfit fixedpoint.Value `json:"floatProfit"`
|
|
|
|
GridProfit fixedpoint.Value `json:"gridProfit"`
|
|
|
|
ArbitrageCount int `json:"arbitrageCount"`
|
2022-11-14 08:28:42 +00:00
|
|
|
TotalFee fixedpoint.Value `json:"totalFee"`
|
|
|
|
Volume fixedpoint.Value `json:"volume"`
|
2022-11-14 08:28:07 +00:00
|
|
|
}
|
|
|
|
|
2022-11-02 10:26:30 +00:00
|
|
|
type Strategy struct {
|
2022-11-10 12:58:46 +00:00
|
|
|
Environment *bbgo.Environment
|
|
|
|
|
2022-11-02 10:26:30 +00:00
|
|
|
// 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.
|
2022-11-10 15:34:55 +00:00
|
|
|
types.Market `json:"-"`
|
2022-11-02 10:26:30 +00:00
|
|
|
|
|
|
|
// These fields will be filled from the config file (it translates YAML to JSON)
|
2022-11-10 15:34:55 +00:00
|
|
|
Symbol string `json:"symbol"`
|
2022-11-02 10:26:30 +00:00
|
|
|
|
|
|
|
// ProfitSpread is the fixed profit spread you want to submit the sell order
|
2022-11-10 15:34:55 +00:00
|
|
|
ProfitSpread fixedpoint.Value `json:"profitSpread"`
|
2022-11-02 10:26:30 +00:00
|
|
|
|
|
|
|
// GridNum is the grid number, how many orders you want to post on the orderbook.
|
2022-11-10 15:34:55 +00:00
|
|
|
GridNum int64 `json:"gridNumber"`
|
2022-11-02 10:26:30 +00:00
|
|
|
|
2022-11-10 15:34:55 +00:00
|
|
|
UpperPrice fixedpoint.Value `json:"upperPrice"`
|
2022-11-02 10:26:30 +00:00
|
|
|
|
2022-11-10 15:34:55 +00:00
|
|
|
LowerPrice fixedpoint.Value `json:"lowerPrice"`
|
2022-11-09 08:25:00 +00:00
|
|
|
|
2022-11-10 15:34:55 +00:00
|
|
|
// QuantityOrAmount embeds the Quantity field and the Amount field
|
|
|
|
// If you set up the Quantity field or the Amount field, you don't need to set the QuoteInvestment and BaseInvestment
|
2022-11-02 10:26:30 +00:00
|
|
|
bbgo.QuantityOrAmount
|
|
|
|
|
2022-11-10 15:34:55 +00:00
|
|
|
// If Quantity and Amount is not set, we can use the quote investment to calculate our quantity.
|
|
|
|
QuoteInvestment fixedpoint.Value `json:"quoteInvestment"`
|
|
|
|
|
|
|
|
// BaseInvestment is the total base quantity you want to place as the sell order.
|
|
|
|
BaseInvestment fixedpoint.Value `json:"baseInvestment"`
|
|
|
|
|
|
|
|
grid *Grid
|
|
|
|
|
2022-11-02 10:26:30 +00:00
|
|
|
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
|
|
|
Position *types.Position `persistence:"position"`
|
|
|
|
|
|
|
|
// 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.ActiveOrderBook
|
|
|
|
|
|
|
|
tradeCollector *bbgo.TradeCollector
|
|
|
|
|
2022-11-10 12:58:46 +00:00
|
|
|
orderExecutor *bbgo.GeneralOrderExecutor
|
|
|
|
|
2022-11-02 10:26:30 +00:00
|
|
|
// groupID is the group ID used for the strategy instance for canceling orders
|
|
|
|
groupID uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Strategy) ID() string {
|
|
|
|
return ID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Strategy) Validate() error {
|
|
|
|
if s.UpperPrice.IsZero() {
|
|
|
|
return errors.New("upperPrice can not be zero, you forgot to set?")
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.LowerPrice.IsZero() {
|
|
|
|
return errors.New("lowerPrice can not be zero, you forgot to set?")
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.UpperPrice.Compare(s.LowerPrice) <= 0 {
|
|
|
|
return fmt.Errorf("upperPrice (%s) should not be less than or equal to lowerPrice (%s)", s.UpperPrice.String(), s.LowerPrice.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.ProfitSpread.Sign() <= 0 {
|
|
|
|
// If profitSpread is empty or its value is negative
|
|
|
|
return fmt.Errorf("profit spread should bigger than 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.GridNum == 0 {
|
|
|
|
return fmt.Errorf("gridNum can not be zero")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.QuantityOrAmount.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|
|
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) handleOrderFilled(o types.Order) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
|
|
|
instanceID := s.InstanceID()
|
|
|
|
|
|
|
|
s.groupID = util.FNV32(instanceID)
|
|
|
|
|
|
|
|
log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
|
|
|
|
|
|
|
|
if s.ProfitStats == nil {
|
|
|
|
s.ProfitStats = types.NewProfitStats(s.Market)
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.Position == nil {
|
|
|
|
s.Position = types.NewPositionFromMarket(s.Market)
|
|
|
|
}
|
|
|
|
|
2022-11-10 12:58:46 +00:00
|
|
|
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
|
|
|
s.orderExecutor.BindEnvironment(s.Environment)
|
|
|
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
|
|
|
s.orderExecutor.Bind()
|
|
|
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
|
|
|
bbgo.Sync(ctx, s)
|
2022-11-02 10:26:30 +00:00
|
|
|
})
|
|
|
|
|
2022-11-10 12:58:46 +00:00
|
|
|
s.grid = NewGrid(s.LowerPrice, s.UpperPrice, fixedpoint.NewFromInt(s.GridNum), s.Market.TickSize)
|
|
|
|
s.grid.CalculateArithmeticPins()
|
2022-11-02 10:26:30 +00:00
|
|
|
|
|
|
|
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
bbgo.Sync(ctx, s)
|
|
|
|
|
|
|
|
// now we can cancel the open orders
|
|
|
|
log.Infof("canceling active orders...")
|
|
|
|
if err := session.Exchange.CancelOrders(context.Background(), s.activeOrders.Orders()...); err != nil {
|
|
|
|
log.WithError(err).Errorf("cancel order error")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
session.UserDataStream.OnStart(func() {
|
2022-11-10 12:58:46 +00:00
|
|
|
if err := s.setupGridOrders(ctx, session); err != nil {
|
|
|
|
log.WithError(err).Errorf("failed to setup grid orders")
|
|
|
|
}
|
2022-11-02 10:26:30 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2022-11-10 12:58:46 +00:00
|
|
|
|
2022-11-15 07:29:30 +00:00
|
|
|
type InvestmentBudget struct {
|
|
|
|
baseInvestment fixedpoint.Value
|
|
|
|
quoteInvestment fixedpoint.Value
|
|
|
|
baseBalance fixedpoint.Value
|
|
|
|
quoteBalance fixedpoint.Value
|
|
|
|
}
|
|
|
|
|
2022-11-17 09:40:59 +00:00
|
|
|
func (s *Strategy) checkRequiredInvestmentByQuantity(baseBalance, quoteBalance, quantity, lastPrice fixedpoint.Value, pins []Pin) (requiredBase, requiredQuote fixedpoint.Value, err error) {
|
2022-11-15 07:29:30 +00:00
|
|
|
// check more investment budget details
|
2022-11-16 04:09:55 +00:00
|
|
|
requiredBase = fixedpoint.Zero
|
|
|
|
requiredQuote = fixedpoint.Zero
|
|
|
|
|
|
|
|
// when we need to place a buy-to-sell conversion order, we need to mark the price
|
|
|
|
buyPlacedPrice := fixedpoint.Zero
|
|
|
|
for i := len(pins) - 1; i >= 0; i-- {
|
2022-11-15 07:29:30 +00:00
|
|
|
pin := pins[i]
|
2022-11-14 09:37:32 +00:00
|
|
|
price := fixedpoint.Value(pin)
|
|
|
|
|
2022-11-15 07:29:30 +00:00
|
|
|
// TODO: add fee if we don't have the platform token. BNB, OKB or MAX...
|
2022-11-14 09:37:32 +00:00
|
|
|
if price.Compare(lastPrice) >= 0 {
|
|
|
|
// for orders that sell
|
|
|
|
// if we still have the base balance
|
2022-11-16 04:09:55 +00:00
|
|
|
if requiredBase.Add(quantity).Compare(baseBalance) <= 0 {
|
2022-11-14 09:37:32 +00:00
|
|
|
requiredBase = requiredBase.Add(quantity)
|
2022-11-15 07:29:30 +00:00
|
|
|
} else if i > 0 { // we do not want to sell at i == 0
|
|
|
|
// convert sell to buy quote and add to requiredQuote
|
2022-11-16 04:09:55 +00:00
|
|
|
nextLowerPin := pins[i-1]
|
2022-11-14 09:37:32 +00:00
|
|
|
nextLowerPrice := fixedpoint.Value(nextLowerPin)
|
|
|
|
requiredQuote = requiredQuote.Add(quantity.Mul(nextLowerPrice))
|
2022-11-16 04:09:55 +00:00
|
|
|
buyPlacedPrice = nextLowerPrice
|
2022-11-14 09:37:32 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-11-15 07:29:30 +00:00
|
|
|
// for orders that buy
|
2022-11-25 07:05:11 +00:00
|
|
|
if !buyPlacedPrice.IsZero() && price.Compare(buyPlacedPrice) == 0 {
|
2022-11-16 04:09:55 +00:00
|
|
|
continue
|
|
|
|
}
|
2022-11-14 09:37:32 +00:00
|
|
|
requiredQuote = requiredQuote.Add(quantity.Mul(price))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-15 07:29:30 +00:00
|
|
|
if requiredBase.Compare(baseBalance) > 0 && requiredQuote.Compare(quoteBalance) > 0 {
|
2022-11-16 05:29:55 +00:00
|
|
|
return requiredBase, requiredQuote, fmt.Errorf("both base balance (%f %s) or quote balance (%f %s) is not enough, required = base %f + quote %f",
|
2022-11-15 07:29:30 +00:00
|
|
|
baseBalance.Float64(), s.Market.BaseCurrency,
|
2022-11-16 05:29:55 +00:00
|
|
|
quoteBalance.Float64(), s.Market.QuoteCurrency,
|
|
|
|
requiredBase.Float64(),
|
|
|
|
requiredQuote.Float64())
|
|
|
|
}
|
|
|
|
|
2022-11-16 07:11:42 +00:00
|
|
|
if requiredBase.Compare(baseBalance) > 0 {
|
|
|
|
return requiredBase, requiredQuote, fmt.Errorf("base balance (%f %s), required = base %f",
|
|
|
|
baseBalance.Float64(), s.Market.BaseCurrency,
|
|
|
|
requiredBase.Float64(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if requiredQuote.Compare(quoteBalance) > 0 {
|
|
|
|
return requiredBase, requiredQuote, fmt.Errorf("quote balance (%f %s) is not enough, required = quote %f",
|
|
|
|
quoteBalance.Float64(), s.Market.QuoteCurrency,
|
|
|
|
requiredQuote.Float64(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return requiredBase, requiredQuote, nil
|
|
|
|
}
|
|
|
|
|
2022-11-17 09:40:59 +00:00
|
|
|
func (s *Strategy) checkRequiredInvestmentByAmount(baseBalance, quoteBalance, amount, lastPrice fixedpoint.Value, pins []Pin) (requiredBase, requiredQuote fixedpoint.Value, err error) {
|
2022-11-16 07:11:42 +00:00
|
|
|
|
|
|
|
// check more investment budget details
|
|
|
|
requiredBase = fixedpoint.Zero
|
|
|
|
requiredQuote = fixedpoint.Zero
|
|
|
|
|
|
|
|
// when we need to place a buy-to-sell conversion order, we need to mark the price
|
|
|
|
buyPlacedPrice := fixedpoint.Zero
|
|
|
|
for i := len(pins) - 1; i >= 0; i-- {
|
|
|
|
pin := pins[i]
|
|
|
|
price := fixedpoint.Value(pin)
|
|
|
|
|
|
|
|
// TODO: add fee if we don't have the platform token. BNB, OKB or MAX...
|
|
|
|
if price.Compare(lastPrice) >= 0 {
|
|
|
|
// for orders that sell
|
|
|
|
// if we still have the base balance
|
|
|
|
quantity := amount.Div(lastPrice)
|
|
|
|
if requiredBase.Add(quantity).Compare(baseBalance) <= 0 {
|
|
|
|
requiredBase = requiredBase.Add(quantity)
|
|
|
|
} else if i > 0 { // we do not want to sell at i == 0
|
|
|
|
// convert sell to buy quote and add to requiredQuote
|
|
|
|
nextLowerPin := pins[i-1]
|
|
|
|
nextLowerPrice := fixedpoint.Value(nextLowerPin)
|
|
|
|
requiredQuote = requiredQuote.Add(quantity.Mul(nextLowerPrice))
|
|
|
|
buyPlacedPrice = nextLowerPrice
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// for orders that buy
|
2022-11-25 07:05:11 +00:00
|
|
|
if !buyPlacedPrice.IsZero() && price.Compare(buyPlacedPrice) == 0 {
|
2022-11-16 07:11:42 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
requiredQuote = requiredQuote.Add(amount)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if requiredBase.Compare(baseBalance) > 0 && requiredQuote.Compare(quoteBalance) > 0 {
|
|
|
|
return requiredBase, requiredQuote, fmt.Errorf("both base balance (%f %s) or quote balance (%f %s) is not enough, required = base %f + quote %f",
|
|
|
|
baseBalance.Float64(), s.Market.BaseCurrency,
|
|
|
|
quoteBalance.Float64(), s.Market.QuoteCurrency,
|
|
|
|
requiredBase.Float64(),
|
|
|
|
requiredQuote.Float64())
|
|
|
|
}
|
|
|
|
|
2022-11-16 05:29:55 +00:00
|
|
|
if requiredBase.Compare(baseBalance) > 0 {
|
|
|
|
return requiredBase, requiredQuote, fmt.Errorf("base balance (%f %s), required = base %f",
|
|
|
|
baseBalance.Float64(), s.Market.BaseCurrency,
|
|
|
|
requiredBase.Float64(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if requiredQuote.Compare(quoteBalance) > 0 {
|
|
|
|
return requiredBase, requiredQuote, fmt.Errorf("quote balance (%f %s) is not enough, required = quote %f",
|
|
|
|
quoteBalance.Float64(), s.Market.QuoteCurrency,
|
|
|
|
requiredQuote.Float64(),
|
|
|
|
)
|
2022-11-14 09:37:32 +00:00
|
|
|
}
|
|
|
|
|
2022-11-16 04:09:55 +00:00
|
|
|
return requiredBase, requiredQuote, nil
|
2022-11-14 08:28:07 +00:00
|
|
|
}
|
|
|
|
|
2022-11-24 07:55:02 +00:00
|
|
|
// setupGridOrders
|
|
|
|
// 1) if quantity or amount is set, we should use quantity/amount directly instead of using investment amount to calculate.
|
|
|
|
// 2) if baseInvestment, quoteInvestment is set, then we should calculate the quantity from the given base investment and quote investment.
|
2022-11-10 12:58:46 +00:00
|
|
|
func (s *Strategy) setupGridOrders(ctx context.Context, session *bbgo.ExchangeSession) error {
|
|
|
|
lastPrice, err := s.getLastTradePrice(ctx, session)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to get the last trade price")
|
|
|
|
}
|
|
|
|
|
2022-11-10 15:34:55 +00:00
|
|
|
// check if base and quote are enough
|
|
|
|
baseBalance, ok := session.Account.Balance(s.Market.BaseCurrency)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("base %s balance not found", s.Market.BaseCurrency)
|
|
|
|
}
|
|
|
|
|
|
|
|
quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("quote %s balance not found", s.Market.QuoteCurrency)
|
|
|
|
}
|
|
|
|
|
|
|
|
totalBase := baseBalance.Available
|
|
|
|
totalQuote := quoteBalance.Available
|
|
|
|
|
2022-11-14 08:28:07 +00:00
|
|
|
// shift 1 grid because we will start from the buy order
|
|
|
|
// if the buy order is filled, then we will submit another sell order at the higher grid.
|
2022-11-17 09:40:59 +00:00
|
|
|
if s.QuantityOrAmount.IsSet() {
|
|
|
|
if quantity := s.QuantityOrAmount.Quantity; !quantity.IsZero() {
|
|
|
|
if _, _, err2 := s.checkRequiredInvestmentByQuantity(totalBase, totalQuote, lastPrice, s.QuantityOrAmount.Quantity, s.grid.Pins); err != nil {
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if amount := s.QuantityOrAmount.Amount; !amount.IsZero() {
|
|
|
|
if _, _, err2 := s.checkRequiredInvestmentByAmount(totalBase, totalQuote, lastPrice, amount, s.grid.Pins); err != nil {
|
|
|
|
return err2
|
|
|
|
}
|
2022-11-10 15:34:55 +00:00
|
|
|
}
|
2022-11-26 16:19:59 +00:00
|
|
|
} else {
|
|
|
|
// TODO: calculate the quantity from the investment configuration
|
2022-11-10 15:34:55 +00:00
|
|
|
}
|
|
|
|
|
2022-11-24 07:55:02 +00:00
|
|
|
if !s.BaseInvestment.IsZero() && !s.QuoteInvestment.IsZero() {
|
|
|
|
if s.BaseInvestment.Compare(totalBase) > 0 {
|
|
|
|
return fmt.Errorf("baseInvestment setup %f is greater than the total base balance %f", s.BaseInvestment.Float64(), totalBase.Float64())
|
|
|
|
}
|
|
|
|
if s.QuoteInvestment.Compare(totalQuote) > 0 {
|
|
|
|
return fmt.Errorf("quoteInvestment setup %f is greater than the total quote balance %f", s.QuoteInvestment.Float64(), totalQuote.Float64())
|
|
|
|
}
|
|
|
|
|
|
|
|
if !s.QuantityOrAmount.IsSet() {
|
|
|
|
// TODO: calculate and override the quantity here
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var buyPlacedPrice = fixedpoint.Zero
|
|
|
|
var pins = s.grid.Pins
|
|
|
|
var usedBase = fixedpoint.Zero
|
|
|
|
var usedQuote = fixedpoint.Zero
|
|
|
|
var submitOrders []types.SubmitOrder
|
|
|
|
for i := len(pins) - 1; i >= 0; i-- {
|
|
|
|
pin := pins[i]
|
2022-11-10 12:58:46 +00:00
|
|
|
price := fixedpoint.Value(pin)
|
2022-11-24 07:55:02 +00:00
|
|
|
quantity := s.QuantityOrAmount.Quantity
|
|
|
|
if quantity.IsZero() {
|
|
|
|
quantity = s.QuantityOrAmount.Amount.Div(price)
|
|
|
|
}
|
2022-11-10 12:58:46 +00:00
|
|
|
|
2022-11-24 07:55:02 +00:00
|
|
|
// TODO: add fee if we don't have the platform token. BNB, OKB or MAX...
|
2022-11-10 12:58:46 +00:00
|
|
|
if price.Compare(lastPrice) >= 0 {
|
2022-11-24 07:55:02 +00:00
|
|
|
if usedBase.Add(quantity).Compare(totalBase) < 0 {
|
|
|
|
submitOrders = append(submitOrders, types.SubmitOrder{
|
2022-11-24 08:35:31 +00:00
|
|
|
Symbol: s.Symbol,
|
|
|
|
Type: types.OrderTypeLimitMaker,
|
|
|
|
Side: types.SideTypeSell,
|
|
|
|
Price: price,
|
|
|
|
Quantity: quantity,
|
|
|
|
Market: s.Market,
|
|
|
|
TimeInForce: types.TimeInForceGTC,
|
|
|
|
Tag: "grid",
|
2022-11-24 07:55:02 +00:00
|
|
|
})
|
|
|
|
usedBase = usedBase.Add(quantity)
|
|
|
|
} else if i > 0 {
|
|
|
|
// next price
|
|
|
|
nextPin := pins[i-1]
|
|
|
|
nextPrice := fixedpoint.Value(nextPin)
|
|
|
|
submitOrders = append(submitOrders, types.SubmitOrder{
|
2022-11-24 08:35:31 +00:00
|
|
|
Symbol: s.Symbol,
|
|
|
|
Type: types.OrderTypeLimitMaker,
|
|
|
|
Side: types.SideTypeBuy,
|
|
|
|
Price: nextPrice,
|
|
|
|
Quantity: quantity,
|
|
|
|
Market: s.Market,
|
|
|
|
TimeInForce: types.TimeInForceGTC,
|
|
|
|
Tag: "grid",
|
2022-11-24 07:55:02 +00:00
|
|
|
})
|
|
|
|
quoteQuantity := quantity.Mul(price)
|
|
|
|
usedQuote = usedQuote.Add(quoteQuantity)
|
|
|
|
buyPlacedPrice = nextPrice
|
2022-11-10 12:58:46 +00:00
|
|
|
}
|
2022-11-24 07:55:02 +00:00
|
|
|
} else {
|
2022-11-25 07:05:11 +00:00
|
|
|
if !buyPlacedPrice.IsZero() && price.Compare(buyPlacedPrice) >= 0 {
|
2022-11-24 08:35:31 +00:00
|
|
|
continue
|
|
|
|
}
|
2022-11-24 07:55:02 +00:00
|
|
|
|
2022-11-24 08:35:31 +00:00
|
|
|
submitOrders = append(submitOrders, types.SubmitOrder{
|
2022-11-24 07:55:02 +00:00
|
|
|
Symbol: s.Symbol,
|
2022-11-24 08:35:31 +00:00
|
|
|
Type: types.OrderTypeLimitMaker,
|
2022-11-24 07:55:02 +00:00
|
|
|
Side: types.SideTypeBuy,
|
|
|
|
Price: price,
|
2022-11-24 08:35:31 +00:00
|
|
|
Quantity: quantity,
|
2022-11-24 07:55:02 +00:00
|
|
|
Market: s.Market,
|
|
|
|
TimeInForce: types.TimeInForceGTC,
|
|
|
|
Tag: "grid",
|
|
|
|
})
|
2022-11-24 08:35:31 +00:00
|
|
|
quoteQuantity := quantity.Mul(price)
|
|
|
|
usedQuote = usedQuote.Add(quoteQuantity)
|
|
|
|
}
|
2022-11-24 07:55:02 +00:00
|
|
|
|
2022-11-24 08:35:31 +00:00
|
|
|
createdOrders, err2 := s.orderExecutor.SubmitOrders(ctx, submitOrders...)
|
|
|
|
if err2 != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-26 16:19:59 +00:00
|
|
|
for _, order := range createdOrders {
|
|
|
|
log.Infof(order.String())
|
|
|
|
}
|
2022-11-10 12:58:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Strategy) getLastTradePrice(ctx context.Context, session *bbgo.ExchangeSession) (fixedpoint.Value, error) {
|
|
|
|
if bbgo.IsBackTesting {
|
|
|
|
price, ok := session.LastPrice(s.Symbol)
|
|
|
|
if !ok {
|
|
|
|
return fixedpoint.Zero, fmt.Errorf("last price of %s not found", s.Symbol)
|
|
|
|
}
|
|
|
|
|
|
|
|
return price, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tickers, err := session.Exchange.QueryTickers(ctx, s.Symbol)
|
|
|
|
if err != nil {
|
|
|
|
return fixedpoint.Zero, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if ticker, ok := tickers[s.Symbol]; ok {
|
|
|
|
if !ticker.Last.IsZero() {
|
|
|
|
return ticker.Last, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fallback to buy price
|
|
|
|
return ticker.Buy, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fixedpoint.Zero, fmt.Errorf("%s ticker price not found", s.Symbol)
|
|
|
|
}
|