Skip to content

Commit

Permalink
Merge pull request #1737 from c9s/c9s/xmaker/ioc-arb
Browse files Browse the repository at this point in the history
FEATURE: [xmaker] implement tryArbitrage
  • Loading branch information
c9s authored Sep 9, 2024
2 parents bc1715f + bd19b63 commit d7ddc9c
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 27 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mattn/go-sqlite3 v1.14.23 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGw
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
Expand Down
152 changes: 126 additions & 26 deletions pkg/strategy/xmaker/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,22 +546,6 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
)
}

if s.EnableArbitrage {
if makerBid, makerAsk, ok := s.makerBook.BestBidAndAsk(); ok {
if makerAsk.Price.Compare(bestBid.Price) <= 0 {
askPvs := s.makerBook.SideBook(types.SideTypeSell)
for _, pv := range askPvs {
if pv.Price.Compare(bestBid.Price) <= 0 {

}
}
// send ioc order for arbitrage
} else if makerBid.Price.Compare(bestAsk.Price) >= 0 {
// send ioc order for arbitrage
}
}
}

// use mid-price for the last price
s.lastPrice = bestBid.Price.Add(bestAsk.Price).Div(two)

Expand Down Expand Up @@ -785,12 +769,19 @@ func (s *Strategy) updateQuote(ctx context.Context) error {

bidExposureInUsd := fixedpoint.Zero
askExposureInUsd := fixedpoint.Zero
bidPrice := quote.BestBidPrice
askPrice := quote.BestAskPrice

bidMarginMetrics.With(s.metricsLabels).Set(quote.BidMargin.Float64())
askMarginMetrics.With(s.metricsLabels).Set(quote.AskMargin.Float64())

if s.EnableArbitrage {
done, err := s.tryArbitrage(ctx, quote, makerBalances, hedgeBalances)
if err != nil {
s.logger.WithError(err).Errorf("unable to arbitrage")
} else if done {
return nil
}
}

if !disableMakerBid {
for i := 0; i < s.NumLayers; i++ {
bidQuantity, err := s.getInitialLayerQuantity(i)
Expand All @@ -810,7 +801,7 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
}
}

bidPrice = s.getLayerPrice(i, types.SideTypeBuy, s.sourceBook, quote, requiredDepth)
bidPrice := s.getLayerPrice(i, types.SideTypeBuy, s.sourceBook, quote, requiredDepth)

if i == 0 {
s.logger.Infof("maker best bid price %f", bidPrice.Float64())
Expand Down Expand Up @@ -859,7 +850,7 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
}
}

askPrice = s.getLayerPrice(i, types.SideTypeSell, s.sourceBook, quote, requiredDepth)
askPrice := s.getLayerPrice(i, types.SideTypeSell, s.sourceBook, quote, requiredDepth)

if i == 0 {
s.logger.Infof("maker best ask price %f", askPrice.Float64())
Expand Down Expand Up @@ -904,14 +895,9 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
return err
}

orderCreateCallback := func(createdOrder types.Order) {
s.orderStore.Add(createdOrder)
s.activeMakerOrders.Add(createdOrder)
}

defer s.tradeCollector.Process()

createdOrders, errIdx, err := bbgo.BatchPlaceOrder(ctx, s.makerSession.Exchange, orderCreateCallback, formattedOrders...)
createdOrders, errIdx, err := bbgo.BatchPlaceOrder(ctx, s.makerSession.Exchange, s.makerOrderCreateCallback, formattedOrders...)
if err != nil {
log.WithError(err).Errorf("unable to place maker orders: %+v", formattedOrders)
return err
Expand All @@ -925,6 +911,120 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
return nil
}

func (s *Strategy) makerOrderCreateCallback(createdOrder types.Order) {
s.orderStore.Add(createdOrder)
s.activeMakerOrders.Add(createdOrder)
}

func aggregatePriceVolumeSliceWithPriceFilter(pvs types.PriceVolumeSlice, filterPrice fixedpoint.Value) types.PriceVolume {
var totalVolume = fixedpoint.Zero
var lastPrice = fixedpoint.Zero
for _, pv := range pvs {
if pv.Price.Compare(filterPrice) > 0 {
break
}

lastPrice = pv.Price
totalVolume = totalVolume.Add(pv.Volume)
}

return types.PriceVolume{
Price: lastPrice,
Volume: totalVolume,
}
}

// tryArbitrage tries to arbitrage between the source and maker exchange
func (s *Strategy) tryArbitrage(ctx context.Context, quote *Quote, makerBalances, hedgeBalances types.BalanceMap) (bool, error) {
marginBidPrice := quote.BestBidPrice.Mul(fixedpoint.One.Sub(quote.BidMargin))
marginAskPrice := quote.BestAskPrice.Mul(fixedpoint.One.Add(quote.AskMargin))

makerBid, makerAsk, ok := s.makerBook.BestBidAndAsk()
if !ok {
return false, nil
}

var iocOrders []types.SubmitOrder
if makerAsk.Price.Compare(marginBidPrice) <= 0 {
quoteBalance, hasQuote := makerBalances[s.makerMarket.QuoteCurrency]
if !hasQuote {
return false, nil
}

askPvs := s.makerBook.SideBook(types.SideTypeSell)
sumPv := aggregatePriceVolumeSliceWithPriceFilter(askPvs, marginBidPrice)
qty := fixedpoint.Min(quoteBalance.Available.Div(sumPv.Price), sumPv.Volume)

if sourceBase, ok := hedgeBalances[s.sourceMarket.BaseCurrency]; ok {
qty = fixedpoint.Min(qty, sourceBase.Available)
} else {
// insufficient hedge base balance for arbitrage
return false, nil
}

iocOrders = append(iocOrders, types.SubmitOrder{
Symbol: s.Symbol,
Type: types.OrderTypeLimit,
Side: types.SideTypeBuy,
Price: sumPv.Price,
Quantity: qty,
TimeInForce: types.TimeInForceIOC,
})

} else if makerBid.Price.Compare(marginAskPrice) >= 0 {
baseBalance, hasBase := makerBalances[s.makerMarket.BaseCurrency]
if !hasBase {
return false, nil
}

bidPvs := s.makerBook.SideBook(types.SideTypeBuy)
sumPv := aggregatePriceVolumeSliceWithPriceFilter(bidPvs, marginAskPrice)
qty := fixedpoint.Min(baseBalance.Available, sumPv.Volume)

if sourceQuote, ok := hedgeBalances[s.sourceMarket.QuoteCurrency]; ok {
qty = fixedpoint.Min(qty, quote.BestAskPrice.Div(sourceQuote.Available))
} else {
// insufficient hedge quote balance for arbitrage
return false, nil
}

// send ioc order for arbitrage
iocOrders = append(iocOrders, types.SubmitOrder{
Symbol: s.Symbol,
Type: types.OrderTypeLimit,
Side: types.SideTypeSell,
Price: sumPv.Price,
Quantity: qty,
TimeInForce: types.TimeInForceIOC,
})
}

if len(iocOrders) == 0 {
return false, nil
}

// send ioc order for arbitrage
formattedOrders, err := s.makerSession.FormatOrders(iocOrders)
if err != nil {
return false, err
}

defer s.tradeCollector.Process()

createdOrders, _, err := bbgo.BatchPlaceOrder(
ctx,
s.makerSession.Exchange,
s.makerOrderCreateCallback,
formattedOrders...)

if err != nil {
return len(createdOrders) > 0, err
}

s.logger.Infof("sent arbitrage IOC order: %+v", createdOrders)
return true, nil
}

func (s *Strategy) adjustHedgeQuantityWithAvailableBalance(
account *types.Account, side types.SideType, quantity, lastPrice fixedpoint.Value,
) fixedpoint.Value {
Expand Down

0 comments on commit d7ddc9c

Please sign in to comment.