From bdd260f208ef391a75fe0dc6944050b3e8cd956b Mon Sep 17 00:00:00 2001 From: martonp Date: Fri, 4 Aug 2023 06:36:01 -0400 Subject: [PATCH] Buck review fixes --- client/asset/btc/btc_test.go | 8 +- client/core/core.go | 13 +- client/mm/mm.go | 164 +++++++--------- client/mm/mm_test.go | 8 +- client/mm/wrapped_core.go | 363 ++++++++++++++++++----------------- 5 files changed, 270 insertions(+), 286 deletions(-) diff --git a/client/asset/btc/btc_test.go b/client/asset/btc/btc_test.go index d8b493e752..54da5e1569 100644 --- a/client/asset/btc/btc_test.go +++ b/client/asset/btc/btc_test.go @@ -2048,13 +2048,15 @@ func testMaxFundingFees(t *testing.T, segwit bool, walletType string) { outputSize = dexbtc.P2PKHOutputSize } - maxFundingFees := wallet.MaxFundingFees(3, useSplitOptions) - expectedFees := feeRateLimit * (inputSize*12 + outputSize*4 + dexbtc.MinimumTxOverhead) + const maxSwaps = 3 + const numInputs = 12 + maxFundingFees := wallet.MaxFundingFees(maxSwaps, useSplitOptions) + expectedFees := feeRateLimit * (inputSize*numInputs + outputSize*(maxSwaps+1) + dexbtc.MinimumTxOverhead) if maxFundingFees != expectedFees { t.Fatalf("unexpected max funding fees. expected %d, got %d", expectedFees, maxFundingFees) } - maxFundingFees = wallet.MaxFundingFees(3, noSplitOptions) + maxFundingFees = wallet.MaxFundingFees(maxSwaps, noSplitOptions) if maxFundingFees != 0 { t.Fatalf("unexpected max funding fees. expected 0, got %d", maxFundingFees) } diff --git a/client/core/core.go b/client/core/core.go index d18a0cbb34..b7366607cf 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -5514,27 +5514,20 @@ func (c *Core) SingleLotFees(form *SingleLotFeesForm) (uint64, uint64, error) { if form.UseMaxFeeRate { dc.assetsMtx.Lock() - swapAsset := dc.assets[wallets.fromWallet.AssetID] + swapAsset, redeemAsset := dc.assets[wallets.fromWallet.AssetID], dc.assets[wallets.toWallet.AssetID] + dc.assetsMtx.Unlock() if swapAsset == nil { - dc.assetsMtx.Unlock() return 0, 0, fmt.Errorf("no asset found for %d", wallets.fromWallet.AssetID) } - swapFeeRate = swapAsset.MaxFeeRate - - redeemAsset := dc.assets[wallets.toWallet.AssetID] if redeemAsset == nil { - dc.assetsMtx.Unlock() return 0, 0, fmt.Errorf("no asset found for %d", wallets.toWallet.AssetID) } - - redeemFeeRate = redeemAsset.MaxFeeRate - dc.assetsMtx.Unlock() + swapFeeRate, redeemFeeRate = swapAsset.MaxFeeRate, redeemAsset.MaxFeeRate } else { swapFeeRate = c.feeSuggestionAny(wallets.fromWallet.AssetID) // server rates only for the swap init if swapFeeRate == 0 { return 0, 0, fmt.Errorf("failed to get swap fee suggestion for %s at %s", wallets.fromWallet.Symbol, form.Host) } - redeemFeeRate = c.feeSuggestionAny(wallets.toWallet.AssetID) // wallet rate or server rate if redeemFeeRate == 0 { return 0, 0, fmt.Errorf("failed to get redeem fee suggestion for %s at %s", wallets.toWallet.Symbol, form.Host) diff --git a/client/mm/mm.go b/client/mm/mm.go index 587552a589..5d3e08038b 100644 --- a/client/mm/mm.go +++ b/client/mm/mm.go @@ -70,7 +70,7 @@ type botBalance struct { // 1. A trade is made: // // - FromAsset: -// DECREASE: LockedFunds + SplitTxFees + FundingFees +// DECREASE: LockedFunds + FundingFees // if isAccountLocker, RefundFeesLockedFunds // // - ToAsset: @@ -122,7 +122,7 @@ type orderInfo struct { order *core.Order initialFundsLocked uint64 lotSize uint64 - //initialRedeemFeesLocked will be > 0 for assets that are account lockers + // initialRedeemFeesLocked will be > 0 for assets that are account lockers // (ETH). This means that the redeem fees will be initially locked, then // the complete redeemed amount will be sent on redemption. initialRedeemFeesLocked uint64 @@ -132,7 +132,7 @@ type orderInfo struct { unusedLockedFundsReturned bool excessFeesReturned bool matchesSeen map[order.MatchID]struct{} - matchesRefunded map[order.MatchID]struct{} + matchesSettled map[order.MatchID]struct{} } // finishedProcessing returns true when the MarketMaker no longer needs to @@ -145,7 +145,7 @@ func (o *orderInfo) finishedProcessing() bool { for _, match := range o.order.Matches { var matchID order.MatchID copy(matchID[:], match.MatchID) - if _, found := o.matchesRefunded[matchID]; !found { + if _, found := o.matchesSettled[matchID]; !found { return false } } @@ -168,6 +168,16 @@ type MarketMaker struct { orders map[order.OrderID]*orderInfo } +// NewMarketMaker creates a new MarketMaker. +func NewMarketMaker(c clientCore, log dex.Logger) (*MarketMaker, error) { + return &MarketMaker{ + core: c, + log: log, + running: atomic.Bool{}, + orders: make(map[order.OrderID]*orderInfo), + }, nil +} + // Running returns true if the MarketMaker is running. func (m *MarketMaker) Running() bool { return m.running.Load() @@ -258,6 +268,8 @@ func validateAndFilterEnabledConfigs(cfgs []*BotConfig) ([]*BotConfig, error) { return enabledCfgs, nil } +// setupBalances makes sure there is sufficient balance to cover all the bots, +// and populates the botBalances map. func (m *MarketMaker) setupBalances(cfgs []*BotConfig) error { m.botBalances = make(map[string]*botBalances, len(cfgs)) @@ -267,27 +279,28 @@ func (m *MarketMaker) setupBalances(cfgs []*BotConfig) error { } balanceTracker := make(map[uint32]*trackedBalance) + trackAsset := func(assetID uint32) error { + if _, found := balanceTracker[assetID]; found { + return nil + } + bal, err := m.core.AssetBalance(assetID) + if err != nil { + return fmt.Errorf("failed to get balance for asset %d: %v", assetID, err) + } + balanceTracker[assetID] = &trackedBalance{ + balanceAvailable: bal.Available, + } + return nil + } for _, cfg := range cfgs { - if _, found := balanceTracker[cfg.BaseAsset]; !found { - bal, err := m.core.AssetBalance(cfg.BaseAsset) - if err != nil { - return fmt.Errorf("failed to get balance for asset %d: %v", cfg.BaseAsset, err) - } - - balanceTracker[cfg.BaseAsset] = &trackedBalance{ - balanceAvailable: bal.Available, - } + err := trackAsset(cfg.BaseAsset) + if err != nil { + return err } - - if _, found := balanceTracker[cfg.QuoteAsset]; !found { - bal, err := m.core.AssetBalance(cfg.QuoteAsset) - if err != nil { - return fmt.Errorf("failed to get balance for asset %d: %v", cfg.QuoteAsset, err) - } - balanceTracker[cfg.QuoteAsset] = &trackedBalance{ - balanceAvailable: bal.Available, - } + err = trackAsset(cfg.QuoteAsset) + if err != nil { + return err } baseBalance := balanceTracker[cfg.BaseAsset] @@ -357,6 +370,11 @@ const ( balTypePendingRefund ) +const ( + balanceModIncrease = true + balanceModDecrease = false +) + // balanceMod is passed to modifyBotBalance to increase or decrease one // of the bot's balances for an asset. type balanceMod struct { @@ -384,55 +402,28 @@ func (m *MarketMaker) modifyBotBalance(botID string, mods []*balanceMod) { continue } - switch mod.typ { - case balTypeAvailable: + newFieldValue := func(balanceType string, initialValue uint64) uint64 { if mod.increase { - assetBalance.Available += mod.amount + return initialValue + mod.amount } else { if assetBalance.Available < mod.amount { - m.log.Errorf("modifyBotBalance: bot %s has insufficient balance for asset %d. "+ - "balance: %d, amount: %d", botID, mod.assetID, assetBalance.Available, mod.amount) - assetBalance.Available = 0 - return + m.log.Errorf("modifyBotBalance: bot %s has insufficient %s for asset %d. "+ + "balance: %d, amount: %d", botID, balanceType, mod.assetID, initialValue, mod.amount) + return 0 } - assetBalance.Available -= mod.amount + return initialValue - mod.amount } + } + + switch mod.typ { + case balTypeAvailable: + assetBalance.Available = newFieldValue("available balance", assetBalance.Available) case balTypeFundingOrder: - if mod.increase { - assetBalance.FundingOrder += mod.amount - } else { - if assetBalance.FundingOrder < mod.amount { - m.log.Errorf("modifyBotBalance: bot %s has insufficient funding order for asset %d. "+ - "balance: %d, amount: %d", botID, mod.assetID, assetBalance.FundingOrder, mod.amount) - assetBalance.FundingOrder = 0 - return - } - assetBalance.FundingOrder -= mod.amount - } + assetBalance.FundingOrder = newFieldValue("funding order", assetBalance.FundingOrder) case balTypePendingRedeem: - if mod.increase { - assetBalance.PendingRedeem += mod.amount - } else { - if assetBalance.PendingRedeem < mod.amount { - m.log.Errorf("modifyBotBalance: bot %s has insufficient pending redeem for asset %d. "+ - "balance: %d, amount: %d", botID, mod.assetID, assetBalance.PendingRedeem, mod.amount) - assetBalance.PendingRedeem = 0 - return - } - assetBalance.PendingRedeem -= mod.amount - } + assetBalance.PendingRedeem = newFieldValue("pending redeem", assetBalance.PendingRedeem) case balTypePendingRefund: - if mod.increase { - assetBalance.PendingRefund += mod.amount - } else { - if assetBalance.PendingRefund < mod.amount { - m.log.Errorf("modifyBotBalance: bot %s has insufficient pending refund for asset %d. "+ - "balance: %d, amount: %d", botID, mod.assetID, assetBalance.PendingRefund, mod.amount) - assetBalance.PendingRefund = 0 - return - } - assetBalance.PendingRefund -= mod.amount - } + assetBalance.PendingRefund = newFieldValue("pending refund", assetBalance.PendingRefund) } } } @@ -500,13 +491,13 @@ func (m *MarketMaker) handleMatchUpdate(match *core.Match, oid dex.Bytes) { var balanceMods []*balanceMod if orderInfo.order.Sell { balanceMods = []*balanceMod{ - {false, orderInfo.order.BaseID, balTypeFundingOrder, match.Qty}, - {true, orderInfo.order.QuoteID, balTypePendingRedeem, calc.BaseToQuote(match.Rate, match.Qty) - maxRedeemFees}, + {balanceModDecrease, orderInfo.order.BaseID, balTypeFundingOrder, match.Qty}, + {balanceModIncrease, orderInfo.order.QuoteID, balTypePendingRedeem, calc.BaseToQuote(match.Rate, match.Qty) - maxRedeemFees}, } } else { balanceMods = []*balanceMod{ - {false, orderInfo.order.QuoteID, balTypeFundingOrder, calc.BaseToQuote(match.Rate, match.Qty)}, - {true, orderInfo.order.BaseID, balTypePendingRedeem, match.Qty - maxRedeemFees}, + {balanceModDecrease, orderInfo.order.QuoteID, balTypeFundingOrder, calc.BaseToQuote(match.Rate, match.Qty)}, + {balanceModIncrease, orderInfo.order.BaseID, balTypePendingRedeem, match.Qty - maxRedeemFees}, } } @@ -517,11 +508,11 @@ func (m *MarketMaker) handleMatchUpdate(match *core.Match, oid dex.Bytes) { return } - if _, handled := orderInfo.matchesRefunded[matchID]; handled { + if _, handled := orderInfo.matchesSettled[matchID]; handled { return } - orderInfo.matchesRefunded[matchID] = struct{}{} + orderInfo.matchesSettled[matchID] = struct{}{} if match.Refund != nil { // TODO: Currently refunds are not handled properly. Core gives no way to @@ -541,13 +532,13 @@ func (m *MarketMaker) handleMatchUpdate(match *core.Match, oid dex.Bytes) { var balanceMods []*balanceMod if orderInfo.order.Sell { balanceMods = []*balanceMod{ - {false, orderInfo.order.QuoteID, balTypePendingRedeem, calc.BaseToQuote(match.Rate, match.Qty) - maxRedeemFees}, - {true, orderInfo.order.BaseID, balTypeAvailable, match.Qty}, + {balanceModDecrease, orderInfo.order.QuoteID, balTypePendingRedeem, calc.BaseToQuote(match.Rate, match.Qty) - maxRedeemFees}, + {balanceModIncrease, orderInfo.order.BaseID, balTypeAvailable, match.Qty}, } } else { balanceMods = []*balanceMod{ - {false, orderInfo.order.BaseID, balTypePendingRedeem, match.Qty - maxRedeemFees}, - {true, orderInfo.order.QuoteID, balTypeAvailable, calc.BaseToQuote(match.Rate, match.Qty)}, + {balanceModDecrease, orderInfo.order.BaseID, balTypePendingRedeem, match.Qty - maxRedeemFees}, + {balanceModIncrease, orderInfo.order.QuoteID, balTypeAvailable, calc.BaseToQuote(match.Rate, match.Qty)}, } } m.log.Tracef("oid: %s, increasing balance due to refund") @@ -568,8 +559,8 @@ func (m *MarketMaker) handleMatchUpdate(match *core.Match, oid dex.Bytes) { m.log.Tracef("oid: %s, increasing balance due to redeem, redeemQty - %v, maxRedeemFees - %v", oid, redeemQty, maxRedeemFees) balanceMods := []*balanceMod{ - {false, redeemAsset, balTypePendingRedeem, redeemQty - maxRedeemFees}, - {true, redeemAsset, balTypeAvailable, redeemQty - maxRedeemFees}, + {balanceModDecrease, redeemAsset, balTypePendingRedeem, redeemQty - maxRedeemFees}, + {balanceModIncrease, redeemAsset, balTypeAvailable, redeemQty - maxRedeemFees}, } m.modifyBotBalance(orderInfo.bot, balanceMods) } @@ -636,8 +627,8 @@ func (m *MarketMaker) handleOrderUpdate(o *core.Order) { o.ID, orderInfo.initialFundsLocked, filledQty, filledLots, maxSwapFees) balanceMods := []*balanceMod{ - {true, fromAsset, balTypeAvailable, orderInfo.initialFundsLocked - usedFunds}, - {false, fromAsset, balTypeFundingOrder, orderInfo.initialFundsLocked - usedFunds}, + {balanceModIncrease, fromAsset, balTypeAvailable, orderInfo.initialFundsLocked - usedFunds}, + {balanceModDecrease, fromAsset, balTypeFundingOrder, orderInfo.initialFundsLocked - usedFunds}, } m.modifyBotBalance(orderInfo.bot, balanceMods) } else { @@ -655,8 +646,8 @@ func (m *MarketMaker) handleOrderUpdate(o *core.Order) { if maxSwapFees > o.FeesPaid.Swap { m.log.Tracef("oid: %s, return excess swap fees, maxSwapFees %v, swap fees %v", o.ID, maxSwapFees, o.FeesPaid.Swap) balanceMods := []*balanceMod{ - {true, fromAsset, balTypeAvailable, maxSwapFees - o.FeesPaid.Swap}, - {false, fromAsset, balTypeFundingOrder, maxSwapFees}, + {balanceModIncrease, fromAsset, balTypeAvailable, maxSwapFees - o.FeesPaid.Swap}, + {balanceModDecrease, fromAsset, balTypeFundingOrder, maxSwapFees}, } m.modifyBotBalance(orderInfo.bot, balanceMods) } else if maxSwapFees < o.FeesPaid.Swap { @@ -669,8 +660,8 @@ func (m *MarketMaker) handleOrderUpdate(o *core.Order) { m.log.Tracef("oid: %s, return excess redeem fees (accountLocker), initialRedeemFeesLocked %v, redemption fees %v", o.ID, orderInfo.initialRedeemFeesLocked, o.FeesPaid.Redemption) balanceMods := []*balanceMod{ - {true, toAsset, balTypeAvailable, orderInfo.initialRedeemFeesLocked - o.FeesPaid.Redemption}, - {false, toAsset, balTypeFundingOrder, orderInfo.initialRedeemFeesLocked}, + {balanceModIncrease, toAsset, balTypeAvailable, orderInfo.initialRedeemFeesLocked - o.FeesPaid.Redemption}, + {balanceModDecrease, toAsset, balTypeFundingOrder, orderInfo.initialRedeemFeesLocked}, } m.modifyBotBalance(orderInfo.bot, balanceMods) } else { @@ -682,7 +673,7 @@ func (m *MarketMaker) handleOrderUpdate(o *core.Order) { if maxRedeemFees > o.FeesPaid.Redemption { m.log.Tracef("oid: %s, return excess redeem fees, maxRedeemFees %v, redemption fees %v", o.ID, maxRedeemFees, o.FeesPaid.Redemption) balanceMods := []*balanceMod{ - {true, toAsset, balTypeAvailable, maxRedeemFees - o.FeesPaid.Redemption}, + {balanceModIncrease, toAsset, balTypeAvailable, maxRedeemFees - o.FeesPaid.Redemption}, } m.modifyBotBalance(orderInfo.bot, balanceMods) } else if maxRedeemFees < o.FeesPaid.Redemption { @@ -793,12 +784,3 @@ func (m *MarketMaker) Stop() { m.die() } } - -func NewMarketMaker(c clientCore, log dex.Logger) (*MarketMaker, error) { - return &MarketMaker{ - core: c, - log: log, - running: atomic.Bool{}, - orders: make(map[order.OrderID]*orderInfo), - }, nil -} diff --git a/client/mm/mm_test.go b/client/mm/mm_test.go index ddbcd75864..636de40ac3 100644 --- a/client/mm/mm_test.go +++ b/client/mm/mm_test.go @@ -1791,7 +1791,7 @@ func testSegregatedCoreTrade(t *testing.T, testMultiTrade bool) { postTradeBalances: map[uint32]*botBalance{ 0: { Available: 100, - FundingOrder: calc.BaseToQuote(5e7, 5e6) + 1400, + FundingOrder: calc.BaseToQuote(5e7, 5e6) + 1000, }, 42: { Available: 5e6, @@ -1878,7 +1878,7 @@ func testSegregatedCoreTrade(t *testing.T, testMultiTrade bool) { }, 42: { Available: 100, - FundingOrder: 5e6 + 1400, + FundingOrder: 5e6 + 1000, }, }, }, @@ -2129,7 +2129,7 @@ func testSegregatedCoreTrade(t *testing.T, testMultiTrade bool) { postTradeBalances: map[uint32]*botBalance{ 0: { Available: 100, - FundingOrder: calc.BaseToQuote(5e7, 5e6) + calc.BaseToQuote(52e7, 5e6) + 2400, + FundingOrder: calc.BaseToQuote(5e7, 5e6) + calc.BaseToQuote(52e7, 5e6) + 2000, }, 42: { Available: 5e6, @@ -2236,7 +2236,7 @@ func testSegregatedCoreTrade(t *testing.T, testMultiTrade bool) { }, 42: { Available: 100, - FundingOrder: 1e7 + 2400, + FundingOrder: 1e7 + 2000, }, }, }, diff --git a/client/mm/wrapped_core.go b/client/mm/wrapped_core.go index c79d42abc5..479252e222 100644 --- a/client/mm/wrapped_core.go +++ b/client/mm/wrapped_core.go @@ -23,12 +23,176 @@ type wrappedCore struct { mm *MarketMaker botID string - core clientCore log dex.Logger } var _ clientCore = (*wrappedCore)(nil) +func (c *wrappedCore) maxBuyQty(host string, base, quote uint32, rate uint64, options map[string]string) (uint64, error) { + baseBalance := c.mm.botBalance(c.botID, base) + quoteBalance := c.mm.botBalance(c.botID, quote) + + mkt, err := c.ExchangeMarket(host, base, quote) + if err != nil { + return 0, err + } + + fundingFees, err := c.MaxFundingFees(quote, 1, options) + if err != nil { + return 0, err + } + + swapFees, redeemFees, err := c.SingleLotFees(&core.SingleLotFeesForm{ + Host: host, + Base: base, + Quote: quote, + UseMaxFeeRate: true, + }) + if err != nil { + return 0, err + } + + if quoteBalance > fundingFees { + quoteBalance -= fundingFees + } else { + quoteBalance = 0 + } + + lotSizeQuote := calc.BaseToQuote(rate, mkt.LotSize) + maxLots := quoteBalance / (lotSizeQuote + swapFees) + + if redeemFees > 0 && c.mm.isAccountLocker(base) { + maxBaseLots := baseBalance / redeemFees + if maxLots > maxBaseLots { + maxLots = maxBaseLots + } + } + + return maxLots * mkt.LotSize, nil +} + +func (c *wrappedCore) maxSellQty(host string, base, quote, numTrades uint32, options map[string]string) (uint64, error) { + baseBalance := c.mm.botBalance(c.botID, base) + quoteBalance := c.mm.botBalance(c.botID, quote) + + mkt, err := c.ExchangeMarket(host, base, quote) + if err != nil { + return 0, err + } + + fundingFees, err := c.MaxFundingFees(base, numTrades, options) + if err != nil { + return 0, err + } + + swapFees, redeemFees, err := c.SingleLotFees(&core.SingleLotFeesForm{ + Host: host, + Base: base, + Quote: quote, + Sell: true, + UseMaxFeeRate: true, + }) + if err != nil { + return 0, err + } + + baseBalance -= fundingFees + maxLots := baseBalance / (mkt.LotSize + swapFees) + if c.mm.isAccountLocker(quote) && redeemFees > 0 { + maxQuoteLots := quoteBalance / redeemFees + if maxLots > maxQuoteLots { + maxLots = maxQuoteLots + } + } + + return maxLots * mkt.LotSize, nil +} + +func (c *wrappedCore) sufficientBalanceForTrade(host string, base, quote uint32, sell bool, rate, qty uint64, options map[string]string) (bool, error) { + var maxQty uint64 + if sell { + var err error + maxQty, err = c.maxSellQty(host, base, quote, 1, options) + if err != nil { + return false, err + } + } else { + var err error + maxQty, err = c.maxBuyQty(host, base, quote, rate, options) + if err != nil { + return false, err + } + } + + return maxQty >= qty, nil +} + +func (c *wrappedCore) sufficientBalanceForMultiSell(host string, base, quote uint32, placements []*core.QtyRate, options map[string]string) (bool, error) { + var totalQty uint64 + for _, placement := range placements { + totalQty += placement.Qty + } + maxQty, err := c.maxSellQty(host, base, quote, uint32(len(placements)), options) + if err != nil { + return false, err + } + return maxQty >= totalQty, nil +} + +func (c *wrappedCore) sufficientBalanceForMultiBuy(host string, base, quote uint32, placements []*core.QtyRate, options map[string]string) (bool, error) { + baseBalance := c.mm.botBalance(c.botID, base) + quoteBalance := c.mm.botBalance(c.botID, quote) + + mkt, err := c.ExchangeMarket(host, base, quote) + if err != nil { + return false, err + } + + swapFees, redeemFees, err := c.SingleLotFees(&core.SingleLotFeesForm{ + Host: host, + Base: base, + Quote: quote, + UseMaxFeeRate: true, + }) + if err != nil { + return false, err + } + + fundingFees, err := c.MaxFundingFees(quote, uint32(len(placements)), options) + if err != nil { + return false, err + } + if quoteBalance < fundingFees { + return false, nil + } + + var totalLots uint64 + remainingBalance := quoteBalance - fundingFees + for _, placement := range placements { + quoteQty := calc.BaseToQuote(placement.Rate, placement.Qty) + numLots := placement.Qty / mkt.LotSize + totalLots += numLots + req := quoteQty + (numLots * swapFees) + if remainingBalance < req { + return false, nil + } + remainingBalance -= req + } + + if c.mm.isAccountLocker(base) && baseBalance < redeemFees*totalLots { + return false, nil + } + + return true, nil +} + +func (c *wrappedCore) sufficientBalanceForMultiTrade(host string, base, quote uint32, sell bool, placements []*core.QtyRate, options map[string]string) (bool, error) { + if sell { + return c.sufficientBalanceForMultiSell(host, base, quote, placements, options) + } + return c.sufficientBalanceForMultiBuy(host, base, quote, placements, options) +} + // Trade checks that the bot has enough balance for the trade, and if not, // immediately returns an error. Otherwise, it forwards the call to the // underlying core. Then, the bot's balance in the balance handler is @@ -47,7 +211,7 @@ func (c *wrappedCore) Trade(pw []byte, form *core.TradeForm) (*core.Order, error return nil, fmt.Errorf("insufficient balance") } - singleLotSwapFees, singleLotRedeemFees, err := c.core.SingleLotFees(&core.SingleLotFeesForm{ + singleLotSwapFees, singleLotRedeemFees, err := c.SingleLotFees(&core.SingleLotFeesForm{ Host: form.Host, Base: form.Base, Quote: form.Quote, @@ -58,12 +222,12 @@ func (c *wrappedCore) Trade(pw []byte, form *core.TradeForm) (*core.Order, error return nil, err } - mkt, err := c.core.ExchangeMarket(form.Host, form.Base, form.Quote) + mkt, err := c.ExchangeMarket(form.Host, form.Base, form.Quote) if err != nil { return nil, err } - o, err := c.core.Trade(pw, form) + o, err := c.clientCore.Trade(pw, form) if err != nil { return nil, err } @@ -81,7 +245,7 @@ func (c *wrappedCore) Trade(pw []byte, form *core.TradeForm) (*core.Order, error singleLotSwapFees: singleLotSwapFees, singleLotRedeemFees: singleLotRedeemFees, lotSize: mkt.LotSize, - matchesRefunded: make(map[order.MatchID]struct{}), + matchesSettled: make(map[order.MatchID]struct{}), matchesSeen: make(map[order.MatchID]struct{}), } c.mm.ordersMtx.Unlock() @@ -97,12 +261,12 @@ func (c *wrappedCore) Trade(pw []byte, form *core.TradeForm) (*core.Order, error } balMods := []*balanceMod{ - {false, fromAsset, balTypeAvailable, o.LockedAmt + fundingFees}, - {true, fromAsset, balTypeFundingOrder, o.LockedAmt + fundingFees}, + {balanceModDecrease, fromAsset, balTypeAvailable, o.LockedAmt + fundingFees}, + {balanceModIncrease, fromAsset, balTypeFundingOrder, o.LockedAmt}, } if o.RedeemLockedAmt > 0 { - balMods = append(balMods, &balanceMod{false, toAsset, balTypeAvailable, o.RedeemLockedAmt}) - balMods = append(balMods, &balanceMod{true, toAsset, balTypeFundingOrder, o.RedeemLockedAmt}) + balMods = append(balMods, &balanceMod{balanceModDecrease, toAsset, balTypeAvailable, o.RedeemLockedAmt}) + balMods = append(balMods, &balanceMod{balanceModIncrease, toAsset, balTypeFundingOrder, o.RedeemLockedAmt}) } c.mm.modifyBotBalance(c.botID, balMods) @@ -110,7 +274,7 @@ func (c *wrappedCore) Trade(pw []byte, form *core.TradeForm) (*core.Order, error } func (c *wrappedCore) MultiTrade(pw []byte, form *core.MultiTradeForm) ([]*core.Order, error) { - enough, err := c.sufficientBalanceForTrades(form.Host, form.Base, form.Quote, form.Sell, form.Placements, form.Options) + enough, err := c.sufficientBalanceForMultiTrade(form.Host, form.Base, form.Quote, form.Sell, form.Placements, form.Options) if err != nil { return nil, err } @@ -118,7 +282,7 @@ func (c *wrappedCore) MultiTrade(pw []byte, form *core.MultiTradeForm) ([]*core. return nil, fmt.Errorf("insufficient balance") } - singleLotSwapFees, singleLotRedeemFees, err := c.core.SingleLotFees(&core.SingleLotFeesForm{ + singleLotSwapFees, singleLotRedeemFees, err := c.SingleLotFees(&core.SingleLotFeesForm{ Host: form.Host, Base: form.Base, Quote: form.Quote, @@ -129,7 +293,7 @@ func (c *wrappedCore) MultiTrade(pw []byte, form *core.MultiTradeForm) ([]*core. return nil, err } - mkt, err := c.core.ExchangeMarket(form.Host, form.Base, form.Quote) + mkt, err := c.ExchangeMarket(form.Host, form.Base, form.Quote) if err != nil { return nil, err } @@ -142,7 +306,7 @@ func (c *wrappedCore) MultiTrade(pw []byte, form *core.MultiTradeForm) ([]*core. } form.MaxLock = c.mm.botBalance(c.botID, fromAsset) - orders, err := c.core.MultiTrade(pw, form) + orders, err := c.clientCore.MultiTrade(pw, form) if err != nil { return nil, err } @@ -162,7 +326,7 @@ func (c *wrappedCore) MultiTrade(pw []byte, form *core.MultiTradeForm) ([]*core. singleLotSwapFees: singleLotSwapFees, singleLotRedeemFees: singleLotRedeemFees, lotSize: mkt.LotSize, - matchesRefunded: make(map[order.MatchID]struct{}), + matchesSettled: make(map[order.MatchID]struct{}), matchesSeen: make(map[order.MatchID]struct{}), } c.mm.ordersMtx.Unlock() @@ -170,7 +334,7 @@ func (c *wrappedCore) MultiTrade(pw []byte, form *core.MultiTradeForm) ([]*core. totalFromLocked += o.LockedAmt totalToLocked += o.RedeemLockedAmt if o.FeesPaid != nil { - totalFromLocked += o.FeesPaid.Funding + fundingFeesPaid += o.FeesPaid.Funding } } @@ -179,57 +343,14 @@ func (c *wrappedCore) MultiTrade(pw []byte, form *core.MultiTradeForm) ([]*core. {true, fromAsset, balTypeFundingOrder, totalFromLocked}, } if totalToLocked > 0 { - balMods = append(balMods, &balanceMod{false, toAsset, balTypeAvailable, totalToLocked}) - balMods = append(balMods, &balanceMod{true, toAsset, balTypeFundingOrder, totalToLocked}) + balMods = append(balMods, &balanceMod{balanceModDecrease, toAsset, balTypeAvailable, totalToLocked}) + balMods = append(balMods, &balanceMod{balanceModIncrease, toAsset, balTypeFundingOrder, totalToLocked}) } c.mm.modifyBotBalance(c.botID, balMods) return orders, nil } -func (c *wrappedCore) maxBuyQty(host string, base, quote uint32, rate uint64, options map[string]string) (uint64, error) { - baseBalance := c.mm.botBalance(c.botID, base) - quoteBalance := c.mm.botBalance(c.botID, quote) - - mkt, err := c.core.ExchangeMarket(host, base, quote) - if err != nil { - return 0, err - } - - fundingFees, err := c.core.MaxFundingFees(quote, 1, options) - if err != nil { - return 0, err - } - - swapFees, redeemFees, err := c.SingleLotFees(&core.SingleLotFeesForm{ - Host: host, - Base: base, - Quote: quote, - UseMaxFeeRate: true, - }) - if err != nil { - return 0, err - } - - if quoteBalance > fundingFees { - quoteBalance -= fundingFees - } else { - quoteBalance = 0 - } - - lotSizeQuote := calc.BaseToQuote(rate, mkt.LotSize) - maxLots := quoteBalance / (lotSizeQuote + swapFees) - - if redeemFees > 0 && c.mm.isAccountLocker(base) { - maxBaseLots := baseBalance / redeemFees - if maxLots > maxBaseLots { - maxLots = maxBaseLots - } - } - - return maxLots * mkt.LotSize, nil -} - // MayBuy returns the maximum quantity of the base asset that the bot can // buy for rate using its balance of the quote asset. func (c *wrappedCore) MaxBuy(host string, base, quote uint32, rate uint64) (*core.MaxOrderEstimate, error) { @@ -237,12 +358,11 @@ func (c *wrappedCore) MaxBuy(host string, base, quote uint32, rate uint64) (*cor if err != nil { return nil, err } - if maxQty == 0 { return nil, fmt.Errorf("insufficient balance") } - orderEstimate, err := c.core.PreOrder(&core.TradeForm{ + orderEstimate, err := c.clientCore.PreOrder(&core.TradeForm{ Host: host, IsLimit: true, Base: base, @@ -261,47 +381,10 @@ func (c *wrappedCore) MaxBuy(host string, base, quote uint32, rate uint64) (*cor }, nil } -func (c *wrappedCore) maxSellQty(host string, base, quote uint32, options map[string]string) (uint64, error) { - baseBalance := c.mm.botBalance(c.botID, base) - quoteBalance := c.mm.botBalance(c.botID, quote) - - mkt, err := c.core.ExchangeMarket(host, base, quote) - if err != nil { - return 0, err - } - - fundingFees, err := c.core.MaxFundingFees(base, 1, options) - if err != nil { - return 0, err - } - - swapFees, redeemFees, err := c.SingleLotFees(&core.SingleLotFeesForm{ - Host: host, - Base: base, - Quote: quote, - Sell: true, - UseMaxFeeRate: true, - }) - if err != nil { - return 0, err - } - - baseBalance -= fundingFees - maxLots := baseBalance / (mkt.LotSize + swapFees) - if c.mm.isAccountLocker(quote) && redeemFees > 0 { - maxQuoteLots := quoteBalance / redeemFees - if maxLots > maxQuoteLots { - maxLots = maxQuoteLots - } - } - - return maxLots * mkt.LotSize, nil -} - // MaxSell returned the maximum quantity of the base asset that the bot can // sell. func (c *wrappedCore) MaxSell(host string, base, quote uint32) (*core.MaxOrderEstimate, error) { - qty, err := c.maxSellQty(host, base, quote, nil) + qty, err := c.maxSellQty(host, base, quote, 1, nil) if err != nil { return nil, err } @@ -309,7 +392,7 @@ func (c *wrappedCore) MaxSell(host string, base, quote uint32) (*core.MaxOrderEs return nil, fmt.Errorf("insufficient balance") } - orderEstimate, err := c.core.PreOrder(&core.TradeForm{ + orderEstimate, err := c.clientCore.PreOrder(&core.TradeForm{ Host: host, IsLimit: true, Sell: true, @@ -340,81 +423,6 @@ func (c *wrappedCore) AssetBalance(assetID uint32) (*core.WalletBalance, error) }, nil } -func (c *wrappedCore) sufficientBalanceForTrade(host string, base, quote uint32, sell bool, rate, qty uint64, options map[string]string) (bool, error) { - var maxQty uint64 - if sell { - var err error - maxQty, err = c.maxSellQty(host, base, quote, options) - if err != nil { - return false, err - } - } else { - var err error - maxQty, err = c.maxBuyQty(host, base, quote, rate, options) - if err != nil { - return false, err - } - } - - return maxQty >= qty, nil -} - -func (c *wrappedCore) sufficientBalanceForTrades(host string, base, quote uint32, sell bool, placements []*core.QtyRate, options map[string]string) (bool, error) { - if sell { - var totalQty uint64 - for _, placement := range placements { - totalQty += placement.Qty - } - maxQty, err := c.maxSellQty(host, base, quote, options) - if err != nil { - return false, err - } - return maxQty >= totalQty, nil - } - - baseBalance := c.mm.botBalance(c.botID, base) - quoteBalance := c.mm.botBalance(c.botID, quote) - - mkt, err := c.core.ExchangeMarket(host, base, quote) - if err != nil { - return false, err - } - - swapFees, redeemFees, err := c.SingleLotFees(&core.SingleLotFeesForm{ - Host: host, - Base: base, - Quote: quote, - UseMaxFeeRate: true, - }) - if err != nil { - return false, err - } - - fundingFees, err := c.core.MaxFundingFees(base, uint32(len(placements)), options) - if err != nil { - return false, err - } - - var totalLots uint64 - remainingBalance := quoteBalance - fundingFees - for _, placement := range placements { - quoteQty := calc.BaseToQuote(placement.Rate, placement.Qty) - numLots := placement.Qty / mkt.LotSize - totalLots += numLots - req := quoteQty + (numLots * swapFees) - if remainingBalance < req { - return false, nil - } - remainingBalance -= req - } - - if c.mm.isAccountLocker(base) && baseBalance < redeemFees*totalLots { - return false, nil - } - - return true, nil -} - // PreOrder checks if the bot's balance is sufficient for the trade, and if it // is, forwards the request to the underlying core. func (c *wrappedCore) PreOrder(form *core.TradeForm) (*core.OrderEstimate, error) { @@ -427,7 +435,7 @@ func (c *wrappedCore) PreOrder(form *core.TradeForm) (*core.OrderEstimate, error return nil, fmt.Errorf("insufficient balance") } - return c.core.PreOrder(form) + return c.clientCore.PreOrder(form) } // wrappedCoreForBot returns a wrappedCore for the specified bot. @@ -435,7 +443,6 @@ func (m *MarketMaker) wrappedCoreForBot(botID string) *wrappedCore { return &wrappedCore{ clientCore: m.core, botID: botID, - core: m.core, log: m.log, mm: m, }