From 7d02c3ace07c8d784f03262850b5dcfc4a6e1211 Mon Sep 17 00:00:00 2001 From: martonp Date: Tue, 1 Aug 2023 09:33:03 -0400 Subject: [PATCH] Decision Info --- client/mm/mm.go | 20 ++++++++ client/mm/mm_basic.go | 115 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 119 insertions(+), 16 deletions(-) diff --git a/client/mm/mm.go b/client/mm/mm.go index 8a313b6ac8..2d88359e3d 100644 --- a/client/mm/mm.go +++ b/client/mm/mm.go @@ -708,6 +708,26 @@ func (m *MarketMaker) handleNotification(n core.Notification) { } } +func (m *MarketMaker) DecisionInfo(cfg *BotConfig) (interface{}, error) { + mkt, err := m.core.ExchangeMarket(cfg.Host, cfg.BaseAsset, cfg.QuoteAsset) + if err != nil { + return nil, fmt.Errorf("failed to get market %s-%d-%d: %w", cfg.Host, cfg.BaseAsset, cfg.QuoteAsset, err) + } + + orderbook, feed, err := m.core.SyncBook(cfg.Host, cfg.BaseAsset, cfg.QuoteAsset) + if err != nil { + return nil, fmt.Errorf("failed to sync order book for market %s-%d-%d: %w", cfg.Host, cfg.BaseAsset, cfg.QuoteAsset, err) + } + defer feed.Close() + + switch { + case cfg.MMCfg != nil: + return getBasicMMDecisionInfo(cfg, mkt, orderbook) + default: + return nil, fmt.Errorf("only basic market making is supported at this time") + } +} + // Run starts the MarketMaker. There can only be one BotConfig per dex market. func (m *MarketMaker) Run(ctx context.Context, cfgs []*BotConfig, pw []byte) error { if !m.running.CompareAndSwap(false, true) { diff --git a/client/mm/mm_basic.go b/client/mm/mm_basic.go index 5bfcd28ecf..6432b6946b 100644 --- a/client/mm/mm_basic.go +++ b/client/mm/mm_basic.go @@ -121,6 +121,80 @@ type MarketMakingConfig struct { SplitBuffer *uint64 `json:"splitBuffer"` } +type basicMMDecisionInfo struct { + MidGap uint64 `json:"midGap"` + OracleRate uint64 `json:"oracleRate"` + FiatRate uint64 `json:"fiatRate"` + BasisPrice uint64 `json:"basisPrice"` + + BaseSingleLotFees uint64 `json:"baseSingleLotFees"` + QuoteSingleLotFees uint64 `json:"quoteSingleLotFees"` + HalfGap uint64 `json:"halfGap"` + + SellOrders []*rateLots `json:"sellOrders"` + BuyOrders []*rateLots `json:"buyOrders"` +} + +func getBasicMMDecisionInfo(cfg *BotConfig, mkt *core.Market, ob dexOrderBook, oracle oracle, + baseFiatRate, quoteFiatRate float64, core clientCore, log dex.Logger) (*basicMMDecisionInfo, error) { + if cfg.MMCfg == nil { + return nil, fmt.Errorf("no market making config") + } + + midGap, err := ob.MidGap() + if err != nil && !errors.Is(err, orderbook.ErrEmptyOrderbook) { + return nil, fmt.Errorf("MidGap error: %v", err) + } + + oracleRate := oracle.getMarketPrice(cfg.BaseAsset, cfg.QuoteAsset) + msgOracleRate := mkt.ConventionalRateToMsg(oracleRate) + fiatRate := uint64(baseFiatRate + quoteFiatRate) // TODO + basisPrice := basisPrice(ob, oracle, cfg.MMCfg, mkt, fiatRate, log) + + info := &basicMMDecisionInfo{ + MidGap: midGap, + OracleRate: msgOracleRate, + FiatRate: fiatRate, + BasisPrice: basisPrice, + } + + if basisPrice == 0 { + return info, nil + } + + var halfSpread uint64 + if needBreakEvenHalfSpread(cfg.MMCfg.GapStrategy) { + hs, baseFees, quoteFees, err := calculateHalfSpread(basisPrice, cfg.Host, cfg.BaseAsset, cfg.QuoteAsset, mkt, core) + if err != nil { + return nil, fmt.Errorf("calculateHalfSpread error: %v", err) + } + halfSpread = hs + info.HalfGap = halfSpread + info.BaseSingleLotFees = baseFees + info.QuoteSingleLotFees = quoteFees + } + + info.BuyOrders = make([]*rateLots, 0, len(cfg.MMCfg.BuyPlacements)) + for _, p := range cfg.MMCfg.BuyPlacements { + rate := orderPrice(basisPrice, halfSpread, cfg.MMCfg.GapStrategy, p.GapFactor, false, mkt) + info.BuyOrders = append(info.BuyOrders, &rateLots{ + Rate: rate, + Lots: p.Lots, + }) + } + + info.SellOrders = make([]*rateLots, 0, len(cfg.MMCfg.SellPlacements)) + for _, p := range cfg.MMCfg.SellPlacements { + rate := orderPrice(basisPrice, halfSpread, cfg.MMCfg.GapStrategy, p.GapFactor, true, mkt) + info.SellOrders = append(info.SellOrders, &rateLots{ + Rate: rate, + Lots: p.Lots, + }) + } + + return info, nil +} + func needBreakEvenHalfSpread(strat GapStrategy) bool { return strat == GapStrategyAbsolutePlus || strat == GapStrategyPercentPlus || strat == GapStrategyMultiplier } @@ -344,37 +418,46 @@ func (m *basicMarketMaker) basisPrice() uint64 { return basisPrice(m.book, m.oracle, m.cfg, m.mkt, m.fiatRateV.Load(), m.log) } -func (m *basicMarketMaker) halfSpread(basisPrice uint64) (uint64, error) { +func calculateHalfSpread(basisPrice uint64, host string, base, quote uint32, mkt *core.Market, c clientCore) (halfSpread, baseFees, quoteFees uint64, err error) { form := &core.SingleLotFeesForm{ - Host: m.host, - Base: m.base, - Quote: m.quote, + Host: host, + Base: base, + Quote: quote, Sell: true, } if basisPrice == 0 { // prevent divide by zero later - return 0, fmt.Errorf("basis price cannot be zero") + return 0, 0, 0, fmt.Errorf("basis price cannot be zero") } - baseFees, quoteFees, err := m.core.SingleLotFees(form) + baseFees, quoteFees, err = c.SingleLotFees(form) if err != nil { - return 0, fmt.Errorf("SingleLotFees error: %v", err) + return 0, 0, 0, fmt.Errorf("SingleLotFees error: %v", err) } form.Sell = false - newQuoteFees, newBaseFees, err := m.core.SingleLotFees(form) + newQuoteFees, newBaseFees, err := c.SingleLotFees(form) if err != nil { - return 0, fmt.Errorf("SingleLotFees error: %v", err) + return 0, 0, 0, fmt.Errorf("SingleLotFees error: %v", err) } baseFees += newBaseFees quoteFees += newQuoteFees g := float64(calc.BaseToQuote(basisPrice, baseFees)+quoteFees) / - float64(baseFees+2*m.mkt.LotSize) * m.mkt.AtomToConv + float64(baseFees+2*mkt.LotSize) * mkt.AtomToConv halfGap := uint64(math.Round(g * calc.RateEncodingFactor)) + return halfGap, baseFees, quoteFees, nil +} + +func (m *basicMarketMaker) halfSpread(basisPrice uint64) (uint64, error) { + halfGap, baseFees, quoteFees, err := calculateHalfSpread(basisPrice, m.host, m.base, m.quote, m.mkt, m.core) + if err != nil { + return 0, err + } + m.log.Tracef("halfSpread: base basis price = %d, lot size = %d, base fees = %d, quote fees = %d, half-gap = %d", basisPrice, m.mkt.LotSize, baseFees, quoteFees, halfGap) @@ -385,8 +468,8 @@ func (m *basicMarketMaker) placeMultiTrade(placements []*rateLots, sell bool) { qtyRates := make([]*core.QtyRate, 0, len(placements)) for _, p := range placements { qtyRates = append(qtyRates, &core.QtyRate{ - Qty: p.lots * m.mkt.LotSize, - Rate: p.rate, + Qty: p.Lots * m.mkt.LotSize, + Rate: p.Rate, }) } @@ -485,8 +568,8 @@ type rebalancer interface { } type rateLots struct { - rate uint64 - lots uint64 + Rate uint64 `json:"rate"` + Lots uint64 `json:"lots"` placementIndex int } @@ -690,8 +773,8 @@ func basicMMRebalance(newEpoch uint64, m rebalancer, c clientCore, cfg *MarketMa } remainingBalance -= requiredPerLot * lotsToPlace rlPlacements = append(rlPlacements, &rateLots{ - rate: placementRate, - lots: lotsToPlace, + Rate: placementRate, + Lots: lotsToPlace, placementIndex: i, }) }