bbgo_origin/pkg/strategy/xpuremaker/strategy.go

198 lines
4.7 KiB
Go
Raw Permalink Normal View History

2020-10-24 10:22:23 +00:00
package xpuremaker
import (
"context"
"math"
"time"
log "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
2021-02-03 01:08:05 +00:00
const ID = "xpuremaker"
var Ten = fixedpoint.NewFromInt(10)
2020-10-24 10:22:23 +00:00
func init() {
2021-02-03 01:08:05 +00:00
bbgo.RegisterStrategy(ID, &Strategy{})
2020-10-24 10:22:23 +00:00
}
type Strategy struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
NumOrders int `json:"numOrders"`
BehindVolume fixedpoint.Value `json:"behindVolume"`
PriceTick fixedpoint.Value `json:"priceTick"`
BaseQuantity fixedpoint.Value `json:"baseQuantity"`
BuySellRatio float64 `json:"buySellRatio"`
book *types.StreamOrderBook
activeOrders map[string]types.Order
2020-10-24 10:22:23 +00:00
}
2021-02-03 01:08:05 +00:00
func (s *Strategy) ID() string {
return ID
}
2020-10-28 09:49:49 +00:00
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
2020-10-24 10:22:23 +00:00
session.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{})
2020-10-28 09:49:49 +00:00
}
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
2020-10-24 10:22:23 +00:00
s.book = types.NewStreamBook(s.Symbol)
s.book.BindStream(session.UserDataStream)
2020-10-24 10:22:23 +00:00
s.activeOrders = make(map[string]types.Order)
2020-10-24 10:22:23 +00:00
// We can move the go routine to the parent level.
go func() {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
2020-10-26 02:01:18 +00:00
s.update(orderExecutor, session)
2020-10-24 10:22:23 +00:00
for {
select {
case <-ctx.Done():
return
case <-s.book.C:
2020-10-26 02:01:18 +00:00
s.update(orderExecutor, session)
2020-10-24 10:22:23 +00:00
case <-ticker.C:
2020-10-26 02:01:18 +00:00
s.update(orderExecutor, session)
2020-10-24 10:22:23 +00:00
}
}
}()
return nil
}
2020-10-26 02:01:18 +00:00
func (s *Strategy) cancelOrders(session *bbgo.ExchangeSession) {
var deletedIDs []string
for clientOrderID, o := range s.activeOrders {
log.Infof("canceling order: %+v", o)
2020-10-26 02:01:18 +00:00
if err := session.Exchange.CancelOrders(context.Background(), o); err != nil {
log.WithError(err).Error("cancel order error")
continue
}
deletedIDs = append(deletedIDs, clientOrderID)
}
s.book.C.Drain(1*time.Second, 3*time.Second)
for _, id := range deletedIDs {
delete(s.activeOrders, id)
}
}
2020-10-26 02:01:18 +00:00
func (s *Strategy) update(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) {
s.cancelOrders(session)
2020-10-24 10:22:23 +00:00
switch s.Side {
case "buy":
2020-10-26 02:08:58 +00:00
s.updateOrders(orderExecutor, session, types.SideTypeBuy)
2020-10-24 10:22:23 +00:00
case "sell":
2020-10-26 02:08:58 +00:00
s.updateOrders(orderExecutor, session, types.SideTypeSell)
2020-10-24 10:22:23 +00:00
case "both":
2020-10-26 02:08:58 +00:00
s.updateOrders(orderExecutor, session, types.SideTypeBuy)
s.updateOrders(orderExecutor, session, types.SideTypeSell)
default:
log.Panicf("undefined side: %s", s.Side)
2020-10-24 10:22:23 +00:00
}
s.book.C.Drain(1*time.Second, 3*time.Second)
2020-10-24 10:22:23 +00:00
}
2020-10-26 02:08:58 +00:00
func (s *Strategy) updateOrders(orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession, side types.SideType) {
var book = s.book.Copy()
var pvs = book.SideBook(side)
2022-06-17 11:19:51 +00:00
if len(pvs) == 0 {
2020-10-26 02:08:58 +00:00
log.Warnf("empty side: %s", side)
2020-10-24 10:22:23 +00:00
return
}
log.Infof("placing order behind volume: %f", s.BehindVolume.Float64())
2020-10-26 02:01:18 +00:00
idx := pvs.IndexByVolumeDepth(s.BehindVolume)
2020-10-26 02:08:58 +00:00
if idx == -1 || idx > len(pvs)-1 {
2020-10-24 10:22:23 +00:00
// do not place orders
log.Warn("depth is not enough")
return
}
2020-10-26 02:01:18 +00:00
var depthPrice = pvs[idx].Price
var orders = s.generateOrders(s.Symbol, side, depthPrice, s.PriceTick, s.BaseQuantity, s.NumOrders)
2020-10-24 10:22:23 +00:00
if len(orders) == 0 {
log.Warn("empty orders")
return
}
createdOrders, err := orderExecutor.SubmitOrders(context.Background(), orders...)
if err != nil {
log.WithError(err).Errorf("order submit error")
return
}
// add created orders to the list
for i, o := range createdOrders {
s.activeOrders[o.ClientOrderID] = createdOrders[i]
}
2020-10-24 10:22:23 +00:00
}
2020-10-26 02:08:58 +00:00
func (s *Strategy) generateOrders(symbol string, side types.SideType, price, priceTick, baseQuantity fixedpoint.Value, numOrders int) (orders []types.SubmitOrder) {
var expBase = fixedpoint.Zero
2020-10-24 10:22:23 +00:00
switch side {
case types.SideTypeBuy:
if priceTick.Sign() > 0 {
priceTick = priceTick.Neg()
2020-10-24 10:22:23 +00:00
}
case types.SideTypeSell:
if priceTick.Sign() < 0 {
priceTick = priceTick.Neg()
2020-10-24 10:22:23 +00:00
}
}
decdigits := priceTick.Abs().NumIntDigits()
step := priceTick.Abs().MulExp(-decdigits + 1)
2020-10-24 10:22:23 +00:00
for i := 0; i < numOrders; i++ {
quantityExp := fixedpoint.NewFromFloat(math.Exp(expBase.Float64()))
volume := baseQuantity.Mul(quantityExp)
amount := volume.Mul(price)
2020-10-24 10:22:23 +00:00
// skip order less than 10usd
if amount.Compare(Ten) < 0 {
log.Warnf("amount too small (< 10usd). price=%s volume=%s amount=%s",
price.String(), volume.String(), amount.String())
2020-10-24 10:22:23 +00:00
continue
}
orders = append(orders, types.SubmitOrder{
Symbol: symbol,
Side: side,
Type: types.OrderTypeLimit,
Price: price,
2020-10-24 10:22:23 +00:00
Quantity: volume,
})
log.Infof("%s order: %s @ %s", side, volume.String(), price.String())
2020-10-24 10:22:23 +00:00
if len(orders) >= numOrders {
break
}
price = price.Add(priceTick)
expBase = expBase.Add(step)
2020-10-24 10:22:23 +00:00
}
return orders
}