Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
mirrorStrategy limits orders placed based on balance on backing excha…
Browse files Browse the repository at this point in the history
…nge (#87), closes #73
  • Loading branch information
nikhilsaraf authored Jan 10, 2019
1 parent ceca42d commit de9b9ca
Showing 1 changed file with 89 additions and 13 deletions.
102 changes: 89 additions & 13 deletions plugins/mirrorStrategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,14 @@ type mirrorStrategy struct {
orderbookDepth int32
perLevelSpread float64
volumeDivideBy float64
tradeAPI api.TradeAPI
exchange api.Exchange
offsetTrades bool
mutex *sync.Mutex
baseSurplus map[model.OrderAction]*assetSurplus // baseSurplus keeps track of any surplus we have of the base asset that needs to be offset on the backing exchange

// uninitialized
maxBackingBase *model.Number
maxBackingQuote *model.Number
}

// ensure this implements api.Strategy
Expand Down Expand Up @@ -120,7 +124,7 @@ func makeMirrorStrategy(sdex *SDEX, pair *model.TradingPair, baseAsset *horizon.
orderbookDepth: config.OrderbookDepth,
perLevelSpread: config.PerLevelSpread,
volumeDivideBy: config.VolumeDivideBy,
tradeAPI: api.TradeAPI(exchange),
exchange: exchange,
offsetTrades: config.OffsetTrades,
mutex: &sync.Mutex{},
baseSurplus: map[model.OrderAction]*assetSurplus{
Expand All @@ -131,21 +135,43 @@ func makeMirrorStrategy(sdex *SDEX, pair *model.TradingPair, baseAsset *horizon.
}

// PruneExistingOffers deletes any extra offers
func (s mirrorStrategy) PruneExistingOffers(buyingAOffers []horizon.Offer, sellingAOffers []horizon.Offer) ([]build.TransactionMutator, []horizon.Offer, []horizon.Offer) {
func (s *mirrorStrategy) PruneExistingOffers(buyingAOffers []horizon.Offer, sellingAOffers []horizon.Offer) ([]build.TransactionMutator, []horizon.Offer, []horizon.Offer) {
return []build.TransactionMutator{}, buyingAOffers, sellingAOffers
}

// PreUpdate changes the strategy's state in prepration for the update
func (s *mirrorStrategy) PreUpdate(maxAssetA float64, maxAssetB float64, trustA float64, trustB float64) error {
return s.recordBalances()
}

func (s *mirrorStrategy) recordBalances() error {
balanceMap, e := s.exchange.GetAccountBalances([]model.Asset{s.backingPair.Base, s.backingPair.Quote})
if e != nil {
return fmt.Errorf("unable to fetch balances for assets: %s", e)
}

// save asset balances from backing exchange to be used when placing offers in offset mode
if baseBalance, ok := balanceMap[s.backingPair.Base]; ok {
s.maxBackingBase = &baseBalance
} else {
return fmt.Errorf("unable to fetch balance for base asset: %s", string(s.backingPair.Base))
}

if quoteBalance, ok := balanceMap[s.backingPair.Quote]; ok {
s.maxBackingQuote = &quoteBalance
} else {
return fmt.Errorf("unable to fetch balance for quote asset: %s", string(s.backingPair.Quote))
}

return nil
}

// UpdateWithOps builds the operations we want performed on the account
func (s mirrorStrategy) UpdateWithOps(
func (s *mirrorStrategy) UpdateWithOps(
buyingAOffers []horizon.Offer,
sellingAOffers []horizon.Offer,
) ([]build.TransactionMutator, error) {
ob, e := s.tradeAPI.GetOrderBook(s.backingPair, s.orderbookDepth)
ob, e := s.exchange.GetOrderBook(s.backingPair, s.orderbookDepth)
if e != nil {
return nil, e
}
Expand All @@ -160,26 +186,40 @@ func (s mirrorStrategy) UpdateWithOps(
asks = asks[:50]
}

sellBalanceCoordinator := balanceCoordinator{
placedUnits: model.NumberConstants.Zero,
backingBalance: s.maxBackingBase,
backingAssetType: "base",
isBackingBuy: false,
}
buyOps, e := s.updateLevels(
buyingAOffers,
bids,
s.sdex.ModifyBuyOffer,
s.sdex.CreateBuyOffer,
(1 - s.perLevelSpread),
true,
sellBalanceCoordinator, // we sell on the backing exchange to offset trades that are bought on the primary exchange
)
if e != nil {
return nil, e
}
log.Printf("num. buyOps in this update: %d\n", len(buyOps))

buyBalanceCoordinator := balanceCoordinator{
placedUnits: model.NumberConstants.Zero,
backingBalance: s.maxBackingQuote,
backingAssetType: "quote",
isBackingBuy: true,
}
sellOps, e := s.updateLevels(
sellingAOffers,
asks,
s.sdex.ModifySellOffer,
s.sdex.CreateSellOffer,
(1 + s.perLevelSpread),
false,
buyBalanceCoordinator, // we buy on the backing exchange to offset trades that are sold on the primary exchange
)
if e != nil {
return nil, e
Expand All @@ -205,6 +245,7 @@ func (s *mirrorStrategy) updateLevels(
createOffer func(baseAsset horizon.Asset, quoteAsset horizon.Asset, price float64, amount float64, incrementalNativeAmountRaw float64) (*build.ManageOfferBuilder, error),
priceMultiplier float64,
hackPriceInvertForBuyOrderChangeCheck bool, // needed because createBuy and modBuy inverts price so we need this for price comparison in doModifyOffer
bc balanceCoordinator,
) ([]build.TransactionMutator, error) {
ops := []build.TransactionMutator{}
deleteOps := []build.TransactionMutator{}
Expand All @@ -215,6 +256,9 @@ func (s *mirrorStrategy) updateLevels(
return nil, e
}
if modifyOp != nil {
if s.offsetTrades && !bc.checkBalance(newOrders[i].Volume, newOrders[i].Price) {
continue
}
ops = append(ops, modifyOp)
}
if deleteOp != nil {
Expand All @@ -224,25 +268,30 @@ func (s *mirrorStrategy) updateLevels(

// create offers for remaining new bids
for i := len(oldOffers); i < len(newOrders); i++ {
price := newOrders[i].Price.Scale(priceMultiplier).AsFloat()
vol := newOrders[i].Volume.Scale(1.0 / s.volumeDivideBy).AsFloat()
price := newOrders[i].Price.Scale(priceMultiplier)
vol := newOrders[i].Volume.Scale(1.0 / s.volumeDivideBy)
incrementalNativeAmountRaw := s.sdex.ComputeIncrementalNativeAmountRaw(true)

if vol < s.backingConstraints.MinBaseVolume.AsFloat() {
log.Printf("skip level creation, baseVolume (%f) < minBaseVolume (%f) of backing exchange\n", vol, s.backingConstraints.MinBaseVolume.AsFloat())
if vol.AsFloat() < s.backingConstraints.MinBaseVolume.AsFloat() {
log.Printf("skip level creation, baseVolume (%s) < minBaseVolume (%s) of backing exchange\n", vol.AsString(), s.backingConstraints.MinBaseVolume.AsString())
continue
}

if s.offsetTrades && !bc.checkBalance(vol, price) {
continue
}
mo, e := createOffer(*s.baseAsset, *s.quoteAsset, price, vol, incrementalNativeAmountRaw)

mo, e := createOffer(*s.baseAsset, *s.quoteAsset, price.AsFloat(), vol.AsFloat(), incrementalNativeAmountRaw)
if e != nil {
return nil, e
}
if mo != nil {
ops = append(ops, *mo)
// update the cached liabilities if we create a valid operation to create an offer
if hackPriceInvertForBuyOrderChangeCheck {
s.sdex.AddLiabilities(*s.quoteAsset, *s.baseAsset, vol*price, vol, incrementalNativeAmountRaw)
s.sdex.AddLiabilities(*s.quoteAsset, *s.baseAsset, vol.Multiply(*price).AsFloat(), vol.AsFloat(), incrementalNativeAmountRaw)
} else {
s.sdex.AddLiabilities(*s.baseAsset, *s.quoteAsset, vol, vol*price, incrementalNativeAmountRaw)
s.sdex.AddLiabilities(*s.baseAsset, *s.quoteAsset, vol.AsFloat(), vol.Multiply(*price).AsFloat(), incrementalNativeAmountRaw)
}
}
}
Expand All @@ -253,6 +302,9 @@ func (s *mirrorStrategy) updateLevels(
return nil, e
}
if modifyOp != nil {
if s.offsetTrades && !bc.checkBalance(newOrders[i].Volume, newOrders[i].Price) {
continue
}
ops = append(ops, modifyOp)
}
if deleteOp != nil {
Expand Down Expand Up @@ -411,7 +463,7 @@ func (s *mirrorStrategy) HandleFill(trade model.Trade) error {
newOrder.Volume.AsFloat(),
newOrder.Volume.Multiply(*newOrder.Price).AsFloat(),
newOrder.Price.AsFloat())
transactionID, e := s.tradeAPI.AddOrder(&newOrder)
transactionID, e := s.exchange.AddOrder(&newOrder)
if e != nil {
return fmt.Errorf("error when offsetting trade (newOrder=%s): %s", newOrder, e)
}
Expand All @@ -437,3 +489,27 @@ func (s *mirrorStrategy) HandleFill(trade model.Trade) error {
transactionID)
return nil
}

// balanceCoordinator coordinates the balances from the backing exchange with orders placed on the primary exchange
type balanceCoordinator struct {
placedUnits *model.Number
backingBalance *model.Number
backingAssetType string
isBackingBuy bool
}

func (b *balanceCoordinator) checkBalance(vol *model.Number, price *model.Number) bool {
additionalUnits := vol
if b.isBackingBuy {
additionalUnits = vol.Multiply(*price)
}

newPlacedUnits := b.placedUnits.Add(*additionalUnits)
if newPlacedUnits.AsFloat() > b.backingBalance.AsFloat() {
log.Printf("skip level creation, not enough balance of %s asset on backing exchange: %s (needs at least %s)\n", b.backingAssetType, b.backingBalance.AsString(), newPlacedUnits.AsString())
return false
}

b.placedUnits = newPlacedUnits
return true
}

0 comments on commit de9b9ca

Please sign in to comment.