Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: [xmaker] implement tryArbitrage #1737

Merged
merged 6 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading