From 4d8aeca285c4a0e84a3f5929dc1f413f6a6b8b5f Mon Sep 17 00:00:00 2001 From: Marton Date: Tue, 12 Nov 2024 00:18:46 +0100 Subject: [PATCH] mmui: Fix bugs related to cex connection and balance (#3060) - When CEX not connected, show error message when clicking allocate or reconfigure buttons. - Add button to remove bot config from mm page. - Unwrap cex connection errors. - Add ablity to modify testbinance balances. --- client/cmd/testbinance/main.go | 98 ++++++++++++++++++++----- client/mm/libxc/binance.go | 4 +- client/mm/mm.go | 18 ++--- client/webserver/jsintl.go | 4 + client/webserver/locales/en-us.go | 1 + client/webserver/site/src/html/mm.tmpl | 16 ++++ client/webserver/site/src/js/doc.ts | 11 +++ client/webserver/site/src/js/forms.ts | 12 ++- client/webserver/site/src/js/locales.ts | 2 + client/webserver/site/src/js/mm.ts | 77 ++++++++++++++----- client/webserver/site/src/js/mmutil.ts | 26 +++---- 11 files changed, 203 insertions(+), 66 deletions(-) diff --git a/client/cmd/testbinance/main.go b/client/cmd/testbinance/main.go index 1459f2f198..174ed651ed 100644 --- a/client/cmd/testbinance/main.go +++ b/client/cmd/testbinance/main.go @@ -12,6 +12,7 @@ import ( "encoding/json" "flag" "fmt" + "io" "math" "math/rand" "net/http" @@ -150,15 +151,50 @@ func makeCoinpapAsset(assetID uint32, symbol, name string) *fiatrates.Coinpaprik } } +// sendBalanceUpdateRequest sends a balance update request to the testbinance server +// running in another process. +func sendBalanceUpdateRequest(coin string, balanceUpdate float64) { + if coin == "" || balanceUpdate == 0 { + fmt.Printf("Invalid balance update request: coin = %q, balanceUpdate = %f\n", coin, balanceUpdate) + return + } + + url := fmt.Sprintf("http://localhost:37346/testbinance/updatebalance?coin=%s&amt=%f", + coin, balanceUpdate) + resp, err := http.Get(url) + if err != nil { + log.Errorf("Error sending balance update request: %v", err) + return + } + + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + fmt.Println("Balance update request failed:", string(body)) + return + } + + fmt.Println("Balance update request sent") +} + func main() { var logDebug, logTrace bool + var coin string + var balanceUpdate float64 flag.Float64Var(&walkingSpeedAdj, "walkspeed", 1.0, "scale the maximum walking speed. default scale of 1.0 is about 3%") flag.Float64Var(&gapRange, "gaprange", 0.04, "a ratio of how much the gap can vary. default is 0.04 => 4%") flag.BoolVar(&logDebug, "debug", false, "use debug logging") flag.BoolVar(&logTrace, "trace", false, "use trace logging") flag.BoolVar(&flappyWS, "flappyws", false, "periodically drop websocket clients and delete subscriptions") + flag.Float64Var(&balanceUpdate, "balupdate", 0, "update the balance of an asset on a testbinance server running as another process") + flag.StringVar(&coin, "coin", "", "coin for testbinance admin update") flag.Parse() + if balanceUpdate != 0 { + sendBalanceUpdateRequest(coin, balanceUpdate) + return + } + switch { case logTrace: log = dex.StdOutLogger("TB", dex.LevelTrace) @@ -312,10 +348,32 @@ func newFakeBinanceServer(ctx context.Context) (*fakeBinance, error) { mux.Get("/ws/{listenKey}", f.handleAccountSubscription) mux.Get("/stream", f.handleMarketStream) + mux.Route("/testbinance", func(r chi.Router) { + r.Get("/updatebalance", f.handleUpdateBalance) + }) return f, nil } +func (f *fakeBinance) handleUpdateBalance(w http.ResponseWriter, r *http.Request) { + coin := r.URL.Query().Get("coin") + amtStr := r.URL.Query().Get("amt") + amt, err := strconv.ParseFloat(amtStr, 64) + if err != nil { + http.Error(w, fmt.Sprintf("invalid amt %q: %v", amtStr, err), http.StatusBadRequest) + return + } + + balUpdate := f.updateBalance(coin, amt) + if balUpdate == nil { + http.Error(w, fmt.Sprintf("no balance to update for %q", coin), http.StatusBadRequest) + return + } + + f.sendBalanceUpdates([]*bntypes.WSBalance{balUpdate}) + w.WriteHeader(http.StatusOK) +} + func (f *fakeBinance) run(ctx context.Context) { // Start a ticker to do book shuffles. @@ -779,6 +837,27 @@ func (f *fakeBinance) handleGetDepositAddress(w http.ResponseWriter, r *http.Req writeJSONWithStatus(w, resp, http.StatusOK) } +func (f *fakeBinance) updateBalance(coin string, amt float64) *bntypes.WSBalance { + f.balancesMtx.Lock() + defer f.balancesMtx.Unlock() + + var balUpdate *bntypes.WSBalance + + for _, b := range f.balances { + if b.Asset == coin { + if amt+b.Free < 0 { + b.Free = 0 + } else { + b.Free += amt + } + balUpdate = (*bntypes.WSBalance)(b) + break + } + } + + return balUpdate +} + func (f *fakeBinance) handleWithdrawal(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() apiKey := extractAPIKey(r) @@ -814,24 +893,7 @@ func (f *fakeBinance) handleWithdrawal(w http.ResponseWriter, r *http.Request) { } f.withdrawalHistoryMtx.Unlock() - var balUpdate *bntypes.WSBalance - debitBalance := func(coin string, amt float64) { - for _, b := range f.balances { - if b.Asset == coin { - if amt > b.Free { - b.Free = 0 - } else { - b.Free -= amt - } - } - balUpdate = (*bntypes.WSBalance)(b) - } - } - - f.balancesMtx.Lock() - debitBalance(coin, amt) - f.balancesMtx.Unlock() - + balUpdate := f.updateBalance(coin, -amt) f.sendBalanceUpdates([]*bntypes.WSBalance{balUpdate}) resp := struct { diff --git a/client/mm/libxc/binance.go b/client/mm/libxc/binance.go index 5f30c3690a..739d8621bf 100644 --- a/client/mm/libxc/binance.go +++ b/client/mm/libxc/binance.go @@ -663,11 +663,11 @@ func (bnc *binance) Connect(ctx context.Context) (*sync.WaitGroup, error) { wg := new(sync.WaitGroup) if err := bnc.getCoinInfo(ctx); err != nil { - return nil, fmt.Errorf("error getting coin info: %v", err) + return nil, fmt.Errorf("error getting coin info: %w", err) } if _, err := bnc.getMarkets(ctx); err != nil { - return nil, fmt.Errorf("error getting markets: %v", err) + return nil, fmt.Errorf("error getting markets: %w", err) } if err := bnc.setBalances(ctx); err != nil { diff --git a/client/mm/mm.go b/client/mm/mm.go index f06ba59653..b9c3cba0dd 100644 --- a/client/mm/mm.go +++ b/client/mm/mm.go @@ -510,21 +510,21 @@ func (m *MarketMaker) connectCEX(ctx context.Context, c *centralizedExchange) er if !cm.On() { c.connectErr = "" if err := cm.ConnectOnce(ctx); err != nil { - c.connectErr = err.Error() - return fmt.Errorf("failed to connect to CEX: %v", err) + c.connectErr = core.UnwrapErr(err).Error() + return fmt.Errorf("failed to connect to CEX: %w", err) } mkts, err := c.Markets(ctx) if err != nil { // Probably can't get here if we didn't error on connect, but // checking anyway. - c.connectErr = err.Error() - return fmt.Errorf("error refreshing markets: %v", err) + c.connectErr = core.UnwrapErr(err).Error() + return fmt.Errorf("error refreshing markets: %w", err) } c.mkts = mkts bals, err := c.Balances(ctx) if err != nil { - c.connectErr = err.Error() - return fmt.Errorf("error getting balances: %v", err) + c.connectErr = core.UnwrapErr(err).Error() + return fmt.Errorf("error getting balances: %w", err) } c.balances = bals } @@ -590,12 +590,12 @@ func (m *MarketMaker) loadCEX(ctx context.Context, cfg *CEXConfig) (*centralized if err != nil { m.log.Errorf("Failed to get markets for %s: %v", cfg.Name, err) c.mkts = make(map[string]*libxc.Market) - c.connectErr = err.Error() + c.connectErr = core.UnwrapErr(err).Error() } if c.balances, err = c.Balances(ctx); err != nil { m.log.Errorf("Failed to get balances for %s: %v", cfg.Name, err) c.balances = make(map[uint32]*libxc.ExchangeBalance) - c.connectErr = err.Error() + c.connectErr = core.UnwrapErr(err).Error() } m.cexes[cfg.Name] = c success = true @@ -1390,7 +1390,7 @@ func (m *MarketMaker) connectedCEX(cexName string) (*centralizedExchange, error) err := m.connectCEX(m.ctx, cex) if err != nil { - return nil, fmt.Errorf("error connecting to CEX: %v", err) + return nil, fmt.Errorf("error connecting to CEX: %w", err) } return cex, nil diff --git a/client/webserver/jsintl.go b/client/webserver/jsintl.go index acc13a4ae8..22555a8e87 100644 --- a/client/webserver/jsintl.go +++ b/client/webserver/jsintl.go @@ -217,6 +217,8 @@ const ( idOrderReportTitle = "ORDER_REPORT_TITLE" idCEXBalances = "CEX_BALANCES" idCausesSelfMatch = "CAUSES_SELF_MATCH" + idCexNotConnected = "CEX_NOT_CONNECTED" + idRemovingBotConfig = "REMOVING_BOT_CONFIG" ) var enUS = map[string]*intl.Translation{ @@ -433,6 +435,8 @@ var enUS = map[string]*intl.Translation{ idOrderReportTitle: {T: "{{ side }} orders report for epoch #{{ epochNum }}"}, idCEXBalances: {T: "{{ cexName }} Balances"}, idCausesSelfMatch: {T: "This order would cause a self-match"}, + idCexNotConnected: {T: "{{ cexName }} not connected"}, + idRemovingBotConfig: {T: "Are you sure you want to remove the config for the {{ baseSymbol }}-{{ quoteSymbol }} market on {{ host }}?"}, } var ptBR = map[string]*intl.Translation{ diff --git a/client/webserver/locales/en-us.go b/client/webserver/locales/en-us.go index 2db4ce7c0a..b8c996d1a1 100644 --- a/client/webserver/locales/en-us.go +++ b/client/webserver/locales/en-us.go @@ -678,4 +678,5 @@ var EnUS = map[string]*intl.Translation{ "Priority": {T: "Priority"}, "Wallet Balances": {T: "Wallet Balances"}, "Placements": {T: "Placements"}, + "removing_bot_config": {T: "Removing Bot Config"}, } diff --git a/client/webserver/site/src/html/mm.tmpl b/client/webserver/site/src/html/mm.tmpl index b7dac7d5d6..03abf8e128 100644 --- a/client/webserver/site/src/html/mm.tmpl +++ b/client/webserver/site/src/html/mm.tmpl @@ -151,8 +151,12 @@ + + {{- /* PROJECTED ALLOCATIONS */ -}}
@@ -609,6 +613,18 @@
{{template "orderReportForm"}}
+ +
+
+
+
[[[removing_bot_config]]]
+
+ +
+ +
+
+
{{- /* END FORMS */ -}} {{template "bottom"}} diff --git a/client/webserver/site/src/js/doc.ts b/client/webserver/site/src/js/doc.ts index 7be2a2d583..4934e526ca 100644 --- a/client/webserver/site/src/js/doc.ts +++ b/client/webserver/site/src/js/doc.ts @@ -245,6 +245,17 @@ export default class Doc { for (const el of els) el.classList.remove('d-hide') } + /* + * showTemporarily shows the specified elements for the specified time, then + * hides it again. + */ + static showTemporarily (timeout: number, ...els: Element[]) { + this.show(...els) + setTimeout(() => { + this.hide(...els) + }, timeout) + } + /* * show or hide the specified elements, based on value of the truthiness of * vis. diff --git a/client/webserver/site/src/js/forms.ts b/client/webserver/site/src/js/forms.ts index 631f207d96..6c5ea715cd 100644 --- a/client/webserver/site/src/js/forms.ts +++ b/client/webserver/site/src/js/forms.ts @@ -2151,14 +2151,13 @@ export class TokenApprovalForm { export class CEXConfigurationForm { form: PageElement page: Record - success: (cexName: string) => void + updated: (cexName: string, success: boolean) => void cexName: string - constructor (form: PageElement, success: (cexName: string) => void) { + constructor (form: PageElement, updated: (cexName: string, success: boolean) => void) { this.form = form - this.success = success + this.updated = updated this.page = Doc.parseTemplate(form) - Doc.bind(this.page.cexSubmit, 'click', () => this.submit()) } @@ -2202,12 +2201,11 @@ export class CEXConfigurationForm { apiSecret: apiSecret }) if (!app().checkResponse(res)) throw res - await app().fetchMMStatus() - this.success(cexName) + this.updated(cexName, true) } catch (e) { Doc.show(page.cexFormErr) page.cexFormErr.textContent = intl.prep(intl.ID_API_ERROR, { msg: e.msg ?? String(e) }) - return + this.updated(cexName, false) } finally { loaded() } diff --git a/client/webserver/site/src/js/locales.ts b/client/webserver/site/src/js/locales.ts index 7c4ca1b7a6..11521164c2 100644 --- a/client/webserver/site/src/js/locales.ts +++ b/client/webserver/site/src/js/locales.ts @@ -217,6 +217,8 @@ export const ID_CEX_TRADE_ERROR = 'CEX_TRADE_ERROR' export const ID_ORDER_REPORT_TITLE = 'ORDER_REPORT_TITLE' export const ID_CEX_BALANCES = 'CEX_BALANCES' export const ID_CAUSES_SELF_MATCH = 'CAUSES_SELF_MATCH' +export const ID_CEX_NOT_CONNECTED = 'CEX_NOT_CONNECTED' +export const ID_REMOVING_BOT_CONFIG = 'REMOVING_BOT_CONFIG' let locale: Locale diff --git a/client/webserver/site/src/js/mm.ts b/client/webserver/site/src/js/mm.ts index a5b048239b..df5f68fcfd 100644 --- a/client/webserver/site/src/js/mm.ts +++ b/client/webserver/site/src/js/mm.ts @@ -4,13 +4,13 @@ import { MMBotStatus, RunStatsNote, RunEventNote, - ExchangeBalance, StartConfig, OrderPlacement, AutoRebalanceConfig, CEXNotification, EpochReportNote, - CEXProblemsNote + CEXProblemsNote, + MarketWithHost } from './registry' import { MM, @@ -185,6 +185,7 @@ export default class MarketMakerPage extends BasePage { cexes: Record twoColumn: boolean runningMMDisplayElements: RunningMMDisplayElements + removingCfg: MarketWithHost | undefined constructor (main: HTMLElement) { super() @@ -198,7 +199,7 @@ export default class MarketMakerPage extends BasePage { Doc.cleanTemplates(page.botTmpl, page.botRowTmpl, page.exchangeRowTmpl) this.forms = new Forms(page.forms) - this.cexConfigForm = new CEXConfigurationForm(page.cexConfigForm, (cexName: string) => this.cexConfigured(cexName)) + this.cexConfigForm = new CEXConfigurationForm(page.cexConfigForm, (cexName: string, success: boolean) => this.cexConfigured(cexName, success)) this.runningMMDisplayElements = { orderReportForm: page.orderReportForm, dexBalancesRowTmpl: page.dexBalancesRowTmpl, @@ -209,6 +210,7 @@ export default class MarketMakerPage extends BasePage { Doc.bind(page.newBot, 'click', () => { this.newBot() }) Doc.bind(page.archivedLogsBtn, 'click', () => { app().loadPage('mmarchives') }) + Doc.bind(page.confirmRemoveConfigBttn, 'click', () => { this.removeCfg() }) this.twoColumn = window.innerWidth >= mediumBreakpoint const ro = new ResizeObserver(() => { this.resized() }) @@ -279,9 +281,7 @@ export default class MarketMakerPage extends BasePage { return (b.runStats?.startTime ?? 0) - (a.runStats?.startTime ?? 0) }) - const startupBalanceCache: Record> = {} - - for (const botStatus of sortedBots) this.addBot(botStatus, startupBalanceCache) + for (const botStatus of sortedBots) this.addBot(botStatus) } async handleCEXNote (n: CEXNotification) { @@ -307,20 +307,45 @@ export default class MarketMakerPage extends BasePage { Doc.unbind(document, 'keyup', this.keyup) } - addBot (botStatus: MMBotStatus, startupBalanceCache?: Record>) { + addBot (botStatus: MMBotStatus) { const { page, bots, sortedBots } = this // Make sure the market still exists. const { config: { baseID, quoteID, host } } = botStatus const [baseSymbol, quoteSymbol] = [app().assets[baseID].symbol, app().assets[quoteID].symbol] const mktID = `${baseSymbol}_${quoteSymbol}` if (!app().exchanges[host]?.markets[mktID]) return - const bot = new Bot(this, this.runningMMDisplayElements, botStatus, startupBalanceCache) + const bot = new Bot(this, this.runningMMDisplayElements, botStatus) page.botRows.appendChild(bot.row.tr) sortedBots.push(bot) bots[bot.id] = bot this.appendBotBox(bot.div) } + confirmRemoveCfg (mwh: MarketWithHost) { + const page = this.page + this.removingCfg = mwh + Doc.hide(page.removeCfgErr) + const baseAsset = app().assets[mwh.baseID] + const quoteAsset = app().assets[mwh.quoteID] + const baseSymbol = baseAsset.symbol.toUpperCase() + const quoteSymbol = quoteAsset.symbol.toUpperCase() + page.confirmRemoveCfgMsg.textContent = intl.prep(intl.ID_REMOVING_BOT_CONFIG, { host: mwh.host, baseSymbol, quoteSymbol }) + this.forms.show(this.page.confirmRemoveForm) + } + + async removeCfg () { + const page = this.page + if (!this.removingCfg) { this.forms.close(); return } + const resp = await MM.removeBotConfig(this.removingCfg.host, this.removingCfg.baseID, this.removingCfg.quoteID) + if (!app().checkResponse(resp)) { + page.removeCfgErr.textContent = intl.prep(intl.ID_API_ERROR, { msg: resp.msg }) + Doc.show(page.removeCfgErr) + return + } + await app().fetchMMStatus() + app().loadPage('mm') + } + appendBotBox (div: PageElement) { const { page: { boxZero, boxOne }, twoColumn } = this const useZeroth = !twoColumn || (boxZero.children.length + boxOne.children.length) % 2 === 0 @@ -352,10 +377,10 @@ export default class MarketMakerPage extends BasePage { app().loadPage('mmsettings') } - async cexConfigured (cexName: string) { + async cexConfigured (cexName: string, success: boolean) { await app().fetchMMStatus() this.updateCexRow(this.cexes[cexName]) - this.forms.close() + if (success) this.forms.close() } updateCexRow (row: CEXRow) { @@ -419,9 +444,8 @@ class Bot extends BotMarket { row: BotRow runDisplay: RunningMarketMakerDisplay - constructor (pg: MarketMakerPage, runningMMElements: RunningMMDisplayElements, status: MMBotStatus, startupBalanceCache?: Record>) { + constructor (pg: MarketMakerPage, runningMMElements: RunningMMDisplayElements, status: MMBotStatus) { super(status.config) - startupBalanceCache = startupBalanceCache ?? {} this.pg = pg const { baseID, quoteID, host, botType, nBuyPlacements, nSellPlacements, cexName } = this this.id = hostedMarketID(host, baseID, quoteID) @@ -452,6 +476,7 @@ class Bot extends BotMarket { Doc.bind(page.startBttn, 'click', () => this.start()) Doc.bind(page.allocationBttn, 'click', () => this.allocate()) Doc.bind(page.reconfigureBttn, 'click', () => this.reconfigure()) + Doc.bind(page.removeBttn, 'click', () => this.pg.confirmRemoveCfg(status.config)) Doc.bind(page.goBackFromAllocation, 'click', () => this.hideAllocationDialog()) Doc.bind(page.marketLink, 'click', () => app().loadPage('markets', { host, baseID, quoteID })) @@ -469,11 +494,11 @@ class Bot extends BotMarket { }) Doc.bind(tr, 'click', () => pg.showBot(this.id)) - this.initialize(startupBalanceCache) + this.initialize() } - async initialize (startupBalanceCache: Record>) { - await super.initialize(startupBalanceCache) + async initialize () { + await super.initialize() this.runDisplay.setBotMarket(this) const { page, host, cexName, botType, div, @@ -632,13 +657,23 @@ class Bot extends BotMarket { * confirm allocations and start the bot. */ allocate () { - const f = this.fundingState() const { page, marketReport: { baseFiatRate, quoteFiatRate }, baseID, quoteID, baseFeeID, quoteFeeID, baseFeeFiatRate, quoteFeeFiatRate, cexName, baseFactor, quoteFactor, baseFeeFactor, quoteFeeFactor, host, mktID } = this + if (cexName) { + const cex = app().mmStatus.cexes[cexName] + if (!cex || !cex.connected) { + page.offError.textContent = intl.prep(intl.ID_CEX_NOT_CONNECTED, { cexName }) + Doc.showTemporarily(3000, page.offError) + return + } + } + + const f = this.fundingState() + const [proposedDexBase, proposedCexBase, baseSlider] = parseFundingOptions(f.base) const [proposedDexQuote, proposedCexQuote, quoteSlider] = parseFundingOptions(f.quote) @@ -823,7 +858,15 @@ class Bot extends BotMarket { } reconfigure () { - const { host, baseID, quoteID, cexName, botType } = this + const { host, baseID, quoteID, cexName, botType, page } = this + if (cexName) { + const cex = app().mmStatus.cexes[cexName] + if (!cex || !cex.connected) { + page.offError.textContent = intl.prep(intl.ID_CEX_NOT_CONNECTED, { cexName }) + Doc.showTemporarily(3000, page.offError) + return + } + } app().loadPage('mmsettings', { host, baseID, quoteID, cexName, botType }) } diff --git a/client/webserver/site/src/js/mmutil.ts b/client/webserver/site/src/js/mmutil.ts index da1a282c16..f3d951bf26 100644 --- a/client/webserver/site/src/js/mmutil.ts +++ b/client/webserver/site/src/js/mmutil.ts @@ -423,8 +423,6 @@ export class BotMarket { baseLots: number quoteLots: number marketReport: MarketReport - cexBaseBalance?: ExchangeBalance - cexQuoteBalance?: ExchangeBalance nBuyPlacements: number nSellPlacements: number @@ -499,20 +497,13 @@ export class BotMarket { } } - async initialize (startupBalanceCache: Record>) { - const { host, baseID, quoteID, lotSizeConv, quoteLotConv, cexName } = this + async initialize () { + const { host, baseID, quoteID, lotSizeConv, quoteLotConv } = this const res = await MM.report(host, baseID, quoteID) const r = this.marketReport = res.report as MarketReport this.lotSizeUSD = lotSizeConv * r.baseFiatRate this.quoteLotUSD = quoteLotConv * r.quoteFiatRate this.proj = this.projectedAllocations() - - if (cexName) { - const b = startupBalanceCache[baseID] = startupBalanceCache[baseID] || MM.cexBalance(cexName, baseID) - const q = startupBalanceCache[quoteID] = startupBalanceCache[quoteID] || MM.cexBalance(cexName, quoteID) - this.cexBaseBalance = await b - this.cexQuoteBalance = await q - } } status () { @@ -529,16 +520,25 @@ export class BotMarket { */ adjustedBalances () { const { - baseID, quoteID, baseFeeID, quoteFeeID, cexBaseBalance, cexQuoteBalance, + baseID, quoteID, baseFeeID, quoteFeeID, cexName, baseFactor, quoteFactor, baseFeeFactor, quoteFeeFactor } = this const [baseWallet, quoteWallet] = [app().walletMap[baseID], app().walletMap[quoteID]] const [bInv, qInv] = [runningBotInventory(baseID), runningBotInventory(quoteID)] + // In these available balance calcs, only subtract the available balance of // running bots, since the locked/reserved/immature is already subtracted // from the wallet's total available balance. let cexBaseAvail = 0 let cexQuoteAvail = 0 + let cexBaseBalance: ExchangeBalance | undefined + let cexQuoteBalance: ExchangeBalance | undefined + if (cexName) { + const cex = app().mmStatus.cexes[cexName] + if (!cex) throw Error('where\'s the cex status?') + cexBaseBalance = cex.balances[baseID] + cexQuoteBalance = cex.balances[quoteID] + } if (cexBaseBalance) cexBaseAvail = (cexBaseBalance.available || 0) - bInv.cex.avail if (cexQuoteBalance) cexQuoteAvail = (cexQuoteBalance.available || 0) - qInv.cex.avail const [dexBaseAvail, dexQuoteAvail] = [baseWallet.balance.available - bInv.dex.avail, quoteWallet.balance.available - qInv.dex.avail] @@ -823,7 +823,7 @@ export class RunningMarketMakerDisplay { const botStatus = app().mmStatus.bots.find(({ config: c }: MMBotStatus) => c.baseID === baseID && c.quoteID === quoteID && c.host === host) if (!botStatus) return const mkt = new BotMarket(botStatus.config) - await mkt.initialize({}) + await mkt.initialize() this.setBotMarket(mkt) }