mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
Merge pull request #1737 from c9s/c9s/xmaker/ioc-arb
FEATURE: [xmaker] implement tryArbitrage
This commit is contained in:
commit
d7ddc9c462
2
go.mod
2
go.mod
|
@ -119,7 +119,7 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // 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/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -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.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 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.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 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
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=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||||
|
|
|
@ -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
|
// use mid-price for the last price
|
||||||
s.lastPrice = bestBid.Price.Add(bestAsk.Price).Div(two)
|
s.lastPrice = bestBid.Price.Add(bestAsk.Price).Div(two)
|
||||||
|
|
||||||
|
@ -785,12 +769,19 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
|
||||||
|
|
||||||
bidExposureInUsd := fixedpoint.Zero
|
bidExposureInUsd := fixedpoint.Zero
|
||||||
askExposureInUsd := fixedpoint.Zero
|
askExposureInUsd := fixedpoint.Zero
|
||||||
bidPrice := quote.BestBidPrice
|
|
||||||
askPrice := quote.BestAskPrice
|
|
||||||
|
|
||||||
bidMarginMetrics.With(s.metricsLabels).Set(quote.BidMargin.Float64())
|
bidMarginMetrics.With(s.metricsLabels).Set(quote.BidMargin.Float64())
|
||||||
askMarginMetrics.With(s.metricsLabels).Set(quote.AskMargin.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 {
|
if !disableMakerBid {
|
||||||
for i := 0; i < s.NumLayers; i++ {
|
for i := 0; i < s.NumLayers; i++ {
|
||||||
bidQuantity, err := s.getInitialLayerQuantity(i)
|
bidQuantity, err := s.getInitialLayerQuantity(i)
|
||||||
|
@ -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 {
|
if i == 0 {
|
||||||
s.logger.Infof("maker best bid price %f", bidPrice.Float64())
|
s.logger.Infof("maker best bid price %f", bidPrice.Float64())
|
||||||
|
@ -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 {
|
if i == 0 {
|
||||||
s.logger.Infof("maker best ask price %f", askPrice.Float64())
|
s.logger.Infof("maker best ask price %f", askPrice.Float64())
|
||||||
|
@ -904,14 +895,9 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
orderCreateCallback := func(createdOrder types.Order) {
|
|
||||||
s.orderStore.Add(createdOrder)
|
|
||||||
s.activeMakerOrders.Add(createdOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer s.tradeCollector.Process()
|
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 {
|
if err != nil {
|
||||||
log.WithError(err).Errorf("unable to place maker orders: %+v", formattedOrders)
|
log.WithError(err).Errorf("unable to place maker orders: %+v", formattedOrders)
|
||||||
return err
|
return err
|
||||||
|
@ -925,6 +911,120 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
|
||||||
return nil
|
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(
|
func (s *Strategy) adjustHedgeQuantityWithAvailableBalance(
|
||||||
account *types.Account, side types.SideType, quantity, lastPrice fixedpoint.Value,
|
account *types.Account, side types.SideType, quantity, lastPrice fixedpoint.Value,
|
||||||
) fixedpoint.Value {
|
) fixedpoint.Value {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user