Skip to content

Commit

Permalink
Merge pull request #1755 from c9s/c9s/xdepthmaker/refactor
Browse files Browse the repository at this point in the history
IMPROVE: [xdepthmaker] improve price solver integration and hedge method
  • Loading branch information
c9s authored Sep 27, 2024
2 parents c8823e9 + 4661ec6 commit 1245560
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 42 deletions.
25 changes: 22 additions & 3 deletions pkg/twap/v2/bbomonitor.go → pkg/bbgo/bbomonitor.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package twap
package bbgo

import (
"time"

"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)

Expand All @@ -14,13 +15,19 @@ type BboMonitor struct {
Ask types.PriceVolume
UpdatedTime time.Time

priceImpactRatio fixedpoint.Value

updateCallbacks []func(bid, ask types.PriceVolume)
}

func NewBboMonitor() *BboMonitor {
return &BboMonitor{}
}

func (m *BboMonitor) SetPriceImpactRatio(ratio fixedpoint.Value) {
m.priceImpactRatio = ratio
}

func (m *BboMonitor) UpdateFromBook(book *types.StreamOrderBook) bool {
bestBid, ok1 := book.BestBid()
bestAsk, ok2 := book.BestAsk()
Expand All @@ -34,11 +41,23 @@ func (m *BboMonitor) UpdateFromBook(book *types.StreamOrderBook) bool {
func (m *BboMonitor) Update(bid, ask types.PriceVolume, t time.Time) bool {
changed := false
if m.Bid.Price.Compare(bid.Price) != 0 || m.Bid.Volume.Compare(bid.Volume) != 0 {
changed = true
if m.priceImpactRatio.IsZero() {
changed = true
} else {
if bid.Price.Sub(m.Bid.Price).Abs().Div(m.Bid.Price).Compare(m.priceImpactRatio) >= 0 {
changed = true
}
}
}

if m.Ask.Price.Compare(ask.Price) != 0 || m.Ask.Volume.Compare(ask.Volume) != 0 {
changed = true
if m.priceImpactRatio.IsZero() {
changed = true
} else {
if ask.Price.Sub(m.Ask.Price).Abs().Div(m.Ask.Price).Compare(m.priceImpactRatio) >= 0 {
changed = true
}
}
}

m.Bid = bid
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions pkg/pricesolver/simple.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pricesolver

import (
"context"
"sync"

log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -69,6 +70,28 @@ func (m *SimplePriceSolver) UpdateFromTrade(trade types.Trade) {
m.Update(trade.Symbol, trade.Price)
}

func (m *SimplePriceSolver) BindStream(stream types.Stream) {
stream.OnKLineClosed(func(k types.KLine) {
m.Update(k.Symbol, k.Close)
})
}

func (m *SimplePriceSolver) UpdateFromTickers(ctx context.Context, ex types.Exchange, symbols ...string) error {
for _, symbol := range symbols {
ticker, err := ex.QueryTicker(ctx, symbol)
if err != nil {
return err
}

price := ticker.GetValidPrice()
if !price.IsZero() {
m.Update(symbol, price)
}
}

return nil
}

func (m *SimplePriceSolver) inferencePrice(asset string, assetPrice fixedpoint.Value, preferredFiats ...string) (fixedpoint.Value, bool) {
// log.Infof("inferencePrice %s = %f", asset, assetPrice.Float64())
quotePrices, ok := m.pricesByBase[asset]
Expand Down
136 changes: 100 additions & 36 deletions pkg/strategy/xdepthmaker/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/c9s/bbgo/pkg/core"
"github.com/c9s/bbgo/pkg/exchange/retry"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/pricesolver"
"github.com/c9s/bbgo/pkg/sigchan"
"github.com/c9s/bbgo/pkg/strategy/common"
"github.com/c9s/bbgo/pkg/strategy/xmaker"
"github.com/c9s/bbgo/pkg/types"
Expand Down Expand Up @@ -185,6 +187,8 @@ type HedgeStrategy string
const (
HedgeStrategyMarket HedgeStrategy = "market"
HedgeStrategyBboCounterParty1 HedgeStrategy = "bbo-counter-party-1"
HedgeStrategyBboCounterParty3 HedgeStrategy = "bbo-counter-party-3"
HedgeStrategyBboCounterParty5 HedgeStrategy = "bbo-counter-party-5"
HedgeStrategyBboQueue1 HedgeStrategy = "bbo-queue-1"
)

Expand All @@ -206,7 +210,8 @@ type Strategy struct {
// HedgeExchange session name
HedgeExchange string `json:"hedgeExchange"`

UpdateInterval types.Duration `json:"updateInterval"`
FastLayerUpdateInterval types.Duration `json:"fastLayerUpdateInterval"`
NumOfFastLayers int `json:"numOfFastLayers"`

HedgeInterval types.Duration `json:"hedgeInterval"`

Expand Down Expand Up @@ -244,6 +249,8 @@ type Strategy struct {
// RecoverTrade tries to find the missing trades via the REStful API
RecoverTrade bool `json:"recoverTrade"`

PriceImpactRatio fixedpoint.Value `json:"priceImpactRatio"`

RecoverTradeScanPeriod types.Duration `json:"recoverTradeScanPeriod"`

NumLayers int `json:"numLayers"`
Expand All @@ -267,11 +274,16 @@ type Strategy struct {

lastSourcePrice fixedpoint.MutexValue

stopC chan struct{}
stopC chan struct{}
fullReplenishTriggerC sigchan.Chan

logger logrus.FieldLogger

connectivityGroup *types.ConnectivityGroup
makerConnectivity, hedgerConnectivity *types.Connectivity
connectivityGroup *types.ConnectivityGroup

priceSolver *pricesolver.SimplePriceSolver
bboMonitor *bbgo.BboMonitor
}

func (s *Strategy) ID() string {
Expand Down Expand Up @@ -317,8 +329,10 @@ func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {
})

hedgeSession.Subscribe(types.KLineChannel, s.HedgeSymbol, types.SubscribeOptions{Interval: "1m"})
hedgeSession.Subscribe(types.KLineChannel, hedgeSession.Exchange.PlatformFeeCurrency()+"USDT", types.SubscribeOptions{Interval: "1m"})

makerSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
makerSession.Subscribe(types.KLineChannel, makerSession.Exchange.PlatformFeeCurrency()+"USDT", types.SubscribeOptions{Interval: "1m"})
}

func (s *Strategy) Validate() error {
Expand All @@ -342,8 +356,12 @@ func (s *Strategy) Validate() error {
}

func (s *Strategy) Defaults() error {
if s.UpdateInterval == 0 {
s.UpdateInterval = types.Duration(5 * time.Second)
if s.FastLayerUpdateInterval == 0 {
s.FastLayerUpdateInterval = types.Duration(5 * time.Second)
}

if s.NumOfFastLayers == 0 {
s.NumOfFastLayers = 5
}

if s.FullReplenishInterval == 0 {
Expand Down Expand Up @@ -391,7 +409,7 @@ func (s *Strategy) Defaults() error {
}

func (s *Strategy) quoteWorker(ctx context.Context) {
updateTicker := time.NewTicker(util.MillisecondsJitter(s.UpdateInterval.Duration(), 200))
updateTicker := time.NewTicker(util.MillisecondsJitter(s.FastLayerUpdateInterval.Duration(), 200))
defer updateTicker.Stop()

fullReplenishTicker := time.NewTicker(util.MillisecondsJitter(s.FullReplenishInterval.Duration(), 200))
Expand All @@ -405,8 +423,6 @@ func (s *Strategy) quoteWorker(ctx context.Context) {

s.updateQuote(ctx, 0)

lastOrderReplenishTime := time.Now()

for {
select {
case <-ctx.Done():
Expand All @@ -416,29 +432,26 @@ func (s *Strategy) quoteWorker(ctx context.Context) {
log.Warnf("%s maker goroutine stopped, due to the stop signal", s.Symbol)
return

case <-s.fullReplenishTriggerC:
// force trigger full replenish
s.updateQuote(ctx, 0)

case <-fullReplenishTicker.C:
s.updateQuote(ctx, 0)
lastOrderReplenishTime = time.Now()

case <-updateTicker.C:
s.updateQuote(ctx, s.NumOfFastLayers)

case sig, ok := <-s.sourceBook.C:
// when any book change event happened
if !ok {
return
}

if time.Since(lastOrderReplenishTime) < 10*time.Second {
continue
}

switch sig.Type {
case types.BookSignalSnapshot:
changed := s.bboMonitor.UpdateFromBook(s.sourceBook)
if changed || sig.Type == types.BookSignalSnapshot {
s.updateQuote(ctx, 0)

case types.BookSignalUpdate:
s.updateQuote(ctx, 5)
}

lastOrderReplenishTime = time.Now()
}
}
}
Expand Down Expand Up @@ -560,15 +573,42 @@ func (s *Strategy) CrossRun(
s.sourceBook = types.NewStreamBook(s.HedgeSymbol, s.hedgeSession.ExchangeName)
s.sourceBook.BindStream(s.hedgeSession.MarketDataStream)

s.priceSolver = pricesolver.NewSimplePriceResolver(s.makerSession.Markets())
s.priceSolver.BindStream(s.hedgeSession.MarketDataStream)
s.priceSolver.BindStream(s.makerSession.MarketDataStream)

s.bboMonitor = bbgo.NewBboMonitor()
if !s.PriceImpactRatio.IsZero() {
s.bboMonitor.SetPriceImpactRatio(s.PriceImpactRatio)
}

if err := s.priceSolver.UpdateFromTickers(ctx, s.makerSession.Exchange,
s.Symbol, s.makerSession.Exchange.PlatformFeeCurrency()+"USDT"); err != nil {
return err
}

if err := s.priceSolver.UpdateFromTickers(ctx, s.hedgeSession.Exchange, s.HedgeSymbol); err != nil {
return err
}

s.makerSession.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval1m, func(k types.KLine) {
s.priceSolver.Update(k.Symbol, k.Close)
feeToken := s.makerSession.Exchange.PlatformFeeCurrency()
if feePrice, ok := s.priceSolver.ResolvePrice(feeToken, "USDT"); ok {
s.Position.SetFeeAverageCost(feeToken, feePrice)
}
}))

s.stopC = make(chan struct{})
s.fullReplenishTriggerC = sigchan.New(1)

makerConnectivity := types.NewConnectivity()
makerConnectivity.Bind(s.makerSession.UserDataStream)
s.makerConnectivity = types.NewConnectivity()
s.makerConnectivity.Bind(s.makerSession.UserDataStream)

hedgerConnectivity := types.NewConnectivity()
hedgerConnectivity.Bind(s.hedgeSession.UserDataStream)
s.hedgerConnectivity = types.NewConnectivity()
s.hedgerConnectivity.Bind(s.hedgeSession.UserDataStream)

connGroup := types.NewConnectivityGroup(makerConnectivity, hedgerConnectivity)
connGroup := types.NewConnectivityGroup(s.makerConnectivity, s.hedgerConnectivity)
s.connectivityGroup = connGroup

if s.RecoverTrade {
Expand All @@ -593,10 +633,12 @@ func (s *Strategy) CrossRun(
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()

bbgo.Notify("Shutting down %s: %s", ID, s.Symbol)

close(s.stopC)

// wait for the quoter to stop
time.Sleep(s.UpdateInterval.Duration())
time.Sleep(s.FastLayerUpdateInterval.Duration())

if err := s.MakerOrderExecutor.GracefulCancel(ctx); err != nil {
log.WithError(err).Errorf("graceful cancel %s order error", s.Symbol)
Expand All @@ -610,8 +652,13 @@ func (s *Strategy) CrossRun(
log.WithError(err).Errorf("unable to cancel all orders")
}

// process collected trades
s.HedgeOrderExecutor.TradeCollector().Process()
s.MakerOrderExecutor.TradeCollector().Process()

bbgo.Sync(ctx, s)
bbgo.Notify("%s: %s position", ID, s.Symbol, s.Position)

bbgo.Notify("Shutdown %s: %s position", ID, s.Symbol, s.Position)
})

return nil
Expand All @@ -635,26 +682,37 @@ func (s *Strategy) Hedge(ctx context.Context, pos fixedpoint.Value) error {
quantity = fixedpoint.Min(s.HedgeMaxOrderQuantity, quantity)
}

defer func() {
s.fullReplenishTriggerC.Emit()
}()

switch s.HedgeStrategy {
case HedgeStrategyMarket:
return s.executeHedgeMarket(ctx, side, quantity)
case HedgeStrategyBboCounterParty1:
return s.executeHedgeBboCounterParty1(ctx, side, quantity)
return s.executeHedgeBboCounterPartyWithIndex(ctx, side, 1, quantity)
case HedgeStrategyBboCounterParty3:
return s.executeHedgeBboCounterPartyWithIndex(ctx, side, 3, quantity)
case HedgeStrategyBboCounterParty5:
return s.executeHedgeBboCounterPartyWithIndex(ctx, side, 5, quantity)
case HedgeStrategyBboQueue1:
return s.executeHedgeBboQueue1(ctx, side, quantity)
default:
return fmt.Errorf("unsupported or invalid hedge strategy setup %q, please check your configuration", s.HedgeStrategy)
}
}

func (s *Strategy) executeHedgeBboCounterParty1(
func (s *Strategy) executeHedgeBboCounterPartyWithIndex(
ctx context.Context,
side types.SideType,
idx int,
quantity fixedpoint.Value,
) error {
price := s.lastSourcePrice.Get()
if sourcePrice := s.getSourceBboPrice(side.Reverse()); sourcePrice.Sign() > 0 {
price = sourcePrice

sideBook := s.sourceBook.SideBook(side.Reverse())
if pv, ok := sideBook.ElemOrLast(idx); ok {
price = pv.Price
}

if price.IsZero() {
Expand Down Expand Up @@ -1068,6 +1126,15 @@ func (s *Strategy) updateQuote(ctx context.Context, maxLayer int) {
return
}

// if it's disconnected or context is canceled, then return
select {
case <-ctx.Done():
return
case <-s.makerConnectivity.DisconnectedC():
return
default:
}

bestBid, bestAsk, hasPrice := s.sourceBook.BestBidAndAsk()
if !hasPrice {
return
Expand Down Expand Up @@ -1126,7 +1193,7 @@ func (s *Strategy) updateQuote(ctx context.Context, maxLayer int) {

_, err = s.MakerOrderExecutor.SubmitOrders(ctx, submitOrders...)
if err != nil {
s.logger.WithError(err).Errorf("order error: %s", err.Error())
s.logger.WithError(err).Errorf("submit order error: %s", err.Error())
return
}
}
Expand All @@ -1141,10 +1208,7 @@ func (s *Strategy) cleanUpOpenOrders(ctx context.Context, session *bbgo.Exchange
return nil
}

log.Infof("found existing open orders:")
types.OrderSlice(openOrders).Print()

return session.Exchange.CancelOrders(ctx, openOrders...)
return tradingutil.UniversalCancelAllOrders(ctx, session.Exchange, s.Symbol, openOrders)
}

func selectSessions2(
Expand Down
Loading

0 comments on commit 1245560

Please sign in to comment.