Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] mm: Live config / balance updates #3081

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions client/mm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ type MarketMakingConfig struct {
CexConfigs []*CEXConfig `json:"cexConfigs"`
}

func (m *MarketMakingConfig) botConfig(mwh *MarketWithHost) *BotConfig {
for _, cfg := range m.BotConfigs {
if cfg.Host == mwh.Host && cfg.BaseID == mwh.BaseID && cfg.QuoteID == mwh.QuoteID {
return cfg
}
}
return nil
}

func (cfg *MarketMakingConfig) Copy() *MarketMakingConfig {
c := &MarketMakingConfig{
BotConfigs: make([]*BotConfig, len(cfg.BotConfigs)),
Expand Down
1 change: 1 addition & 0 deletions client/mm/exchange_adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3590,6 +3590,7 @@ func (u *unifiedExchangeAdaptor) updateConfig(cfg *BotConfig) {

func (u *unifiedExchangeAdaptor) updateInventory(balanceDiffs *BotInventoryDiffs) {
u.updateInventoryEvent(u.applyInventoryDiffs(balanceDiffs))
u.sendStatsUpdate()
}

func (u *unifiedExchangeAdaptor) Book() (buys, sells []*core.MiniOrder, _ error) {
Expand Down
51 changes: 42 additions & 9 deletions client/mm/mm.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,31 @@ func (m *MarketMaker) MarketReport(host string, baseID, quoteID uint32) (*Market
}, nil
}

// MaxFundingFees returns the maximum funding fees for a bot on a market.
// There must be a bot config saved in the default config file for the
// market.
func (m *MarketMaker) MaxFundingFees(mwh *MarketWithHost) (buyFees, sellFees uint64, err error) {
cfg := m.defaultConfig()
botCfg := cfg.botConfig(mwh)
if botCfg == nil {
return 0, 0, fmt.Errorf("no bot config found for %s", mwh)
}

maxBuyPlacements, maxSellPlacements := botCfg.maxPlacements()

buyFundingFees, err := m.core.MaxFundingFees(mwh.QuoteID, mwh.Host, maxBuyPlacements, botCfg.QuoteWalletOptions)
if err != nil {
return 0, 0, fmt.Errorf("failed to get buy funding fees: %w", err)
}

sellFundingFees, err := m.core.MaxFundingFees(mwh.BaseID, mwh.Host, maxSellPlacements, botCfg.BaseWalletOptions)
if err != nil {
return 0, 0, fmt.Errorf("failed to get sell funding fees: %w", err)
}

return buyFundingFees, sellFundingFees, nil
}

func (m *MarketMaker) loginAndUnlockWallets(pw []byte, cfg *BotConfig) error {
err := m.core.Login(pw)
if err != nil {
Expand Down Expand Up @@ -1085,12 +1110,13 @@ func (m *MarketMaker) UpdateRunningBotInventory(mkt *MarketWithHost, balanceDiff
}

if err := rb.withPause(func() error {
rb.bot.updateInventory(balanceDiffs)
rb.updateInventory(balanceDiffs)
return nil
}); err != nil {
rb.cm.Disconnect()
return fmt.Errorf("configuration update error. bot stopped: %w", err)
}

return nil
}

Expand Down Expand Up @@ -1126,12 +1152,15 @@ func (m *MarketMaker) UpdateRunningBotCfg(cfg *BotConfig, balanceDiffs *BotInven

var stoppedOracle, startedOracle, updateSuccess bool
defer func() {
if updateSuccess {
return
if updateSuccess && saveUpdate {
m.updateDefaultBotConfig(cfg)
}
if startedOracle {

if !updateSuccess && startedOracle {
m.oracle.stopAutoSyncingMarket(cfg.BaseID, cfg.QuoteID)
} else if stoppedOracle {
}

if !updateSuccess && stoppedOracle {
err := m.oracle.startAutoSyncingMarket(oldCfg.BaseID, oldCfg.QuoteID)
if err != nil {
m.log.Errorf("Error restarting oracle for %s: %v", mkt, err)
Expand Down Expand Up @@ -1711,10 +1740,14 @@ func (m *MarketMaker) availableBalances(mkt *MarketWithHost, cexCfg *CEXConfig)
// AvailableBalances returns the available balances of assets relevant to
// market making on the specified market on the DEX (including fee assets),
// and optionally a CEX depending on the configured strategy.
func (m *MarketMaker) AvailableBalances(mkt *MarketWithHost, alternateConfigPath *string) (dexBalances, cexBalances map[uint32]uint64, _ error) {
_, cexCfg, err := m.configsForMarket(mkt, alternateConfigPath)
if err != nil {
return nil, nil, err
func (m *MarketMaker) AvailableBalances(mkt *MarketWithHost, cexName *string) (dexBalances, cexBalances map[uint32]uint64, _ error) {
var cexCfg *CEXConfig
if cexName != nil && *cexName != "" {
cex := m.cexes[*cexName]
if cex == nil {
return nil, nil, fmt.Errorf("CEX %s not found", *cexName)
}
cexCfg = cex.CEXConfig
}

return m.availableBalances(mkt, cexCfg)
Expand Down
2 changes: 1 addition & 1 deletion client/rpcserver/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ func handleMMAvailableBalances(s *RPCServer, params *RawParams) *msgjson.Respons
return usage(mmAvailableBalancesRoute, err)
}

dexBalances, cexBalances, err := s.mm.AvailableBalances(form.mkt, &form.cfgFilePath)
dexBalances, cexBalances, err := s.mm.AvailableBalances(form.mkt, form.cexName)
if err != nil {
resErr := msgjson.NewError(msgjson.RPCMMAvailableBalancesError, "unable to get available balances: %v", err)
return createResponse(mmAvailableBalancesRoute, nil, resErr)
Expand Down
12 changes: 7 additions & 5 deletions client/rpcserver/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ type addRemovePeerForm struct {
}

type mmAvailableBalancesForm struct {
cfgFilePath string
mkt *mm.MarketWithHost
mkt *mm.MarketWithHost
cexName *string
}

type startBotForm struct {
Expand Down Expand Up @@ -877,16 +877,18 @@ func parseMktWithHost(host, baseID, quoteID string) (*mm.MarketWithHost, error)
}

func parseMMAvailableBalancesArgs(params *RawParams) (*mmAvailableBalancesForm, error) {
if err := checkNArgs(params, []int{0}, []int{4}); err != nil {
if err := checkNArgs(params, []int{0}, []int{3}); err != nil {
return nil, err
}
form := new(mmAvailableBalancesForm)
form.cfgFilePath = params.Args[0]
mkt, err := parseMktWithHost(params.Args[1], params.Args[2], params.Args[3])
mkt, err := parseMktWithHost(params.Args[0], params.Args[1], params.Args[2])
if err != nil {
return nil, err
}
form.mkt = mkt
if len(params.Args) > 3 {
form.cexName = &params.Args[3]
}
return form, nil
}

Expand Down
82 changes: 82 additions & 0 deletions client/webserver/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/config"
"decred.org/dcrdex/dex/encode"
"github.com/davecgh/go-spew/spew"
)

var zero = encode.ClearBytes
Expand Down Expand Up @@ -1749,6 +1750,54 @@ func (s *WebServer) apiStakeStatus(w http.ResponseWriter, r *http.Request) {
})
}

func (s *WebServer) apiAvailableBalances(w http.ResponseWriter, r *http.Request) {
var req struct {
Market *mm.MarketWithHost `json:"market"`
CEXName *string `json:"cexName,omitempty"`
}
if !readPost(w, r, &req) {
return
}
dexBalances, cexBalances, err := s.mm.AvailableBalances(req.Market, req.CEXName)
if err != nil {
s.writeAPIError(w, fmt.Errorf("error fetching available balances: %w", err))
return
}

writeJSON(w, &struct {
OK bool `json:"ok"`
DEXBalances map[uint32]uint64 `json:"dexBalances"`
CEXBalances map[uint32]uint64 `json:"cexBalances"`
}{
OK: true,
DEXBalances: dexBalances,
CEXBalances: cexBalances,
})
}

func (s *WebServer) apiMaxFundingFees(w http.ResponseWriter, r *http.Request) {
var req struct {
Market *mm.MarketWithHost `json:"market"`
}
if !readPost(w, r, &req) {
return
}
buyFees, sellFees, err := s.mm.MaxFundingFees(req.Market)
if err != nil {
s.writeAPIError(w, fmt.Errorf("error getting max funding fees: %w", err))
return
}
writeJSON(w, &struct {
OK bool `json:"ok"`
BuyFees uint64 `json:"buyFees"`
SellFees uint64 `json:"sellFees"`
}{
OK: true,
BuyFees: buyFees,
SellFees: sellFees,
})
}

func (s *WebServer) apiSetVSP(w http.ResponseWriter, r *http.Request) {
var req struct {
AssetID uint32 `json:"assetID"`
Expand Down Expand Up @@ -1965,6 +2014,39 @@ func (s *WebServer) apiUpdateBotConfig(w http.ResponseWriter, r *http.Request) {
writeJSON(w, simpleAck())
}

func (s *WebServer) apiUpdateRunningBotConfig(w http.ResponseWriter, r *http.Request) {
var updatedCfg *mm.BotConfig
if !readPost(w, r, &updatedCfg) {
s.writeAPIError(w, fmt.Errorf("failed to read config"))
return
}

spew.Dump("apiUpdateRunningBotConfig", updatedCfg)

if err := s.mm.UpdateRunningBotCfg(updatedCfg, nil, true); err != nil {
s.writeAPIError(w, err)
return
}

writeJSON(w, simpleAck())
}

func (s *WebServer) apiUpdateBotInventory(w http.ResponseWriter, r *http.Request) {
var form struct {
Market *mm.MarketWithHost `json:"market"`
Diffs *mm.BotInventoryDiffs `json:"diffs"`
}
if !readPost(w, r, &form) {
s.writeAPIError(w, fmt.Errorf("failed to read form"))
return
}
if err := s.mm.UpdateRunningBotInventory(form.Market, form.Diffs); err != nil {
s.writeAPIError(w, err)
return
}
writeJSON(w, simpleAck())
}

func (s *WebServer) apiRemoveBotConfig(w http.ResponseWriter, r *http.Request) {
var form struct {
Host string `json:"host"`
Expand Down
2 changes: 2 additions & 0 deletions client/webserver/jsintl.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ const (
idCausesSelfMatch = "CAUSES_SELF_MATCH"
idCexNotConnected = "CEX_NOT_CONNECTED"
idDeleteBot = "DELETE_BOT"
idAllocationFormTitle = "ALLOCATION_FORM_TITLE"
)

var enUS = map[string]*intl.Translation{
Expand Down Expand Up @@ -437,6 +438,7 @@ var enUS = map[string]*intl.Translation{
idCausesSelfMatch: {T: "This order would cause a self-match"},
idCexNotConnected: {T: "{{ cexName }} not connected"},
idDeleteBot: {T: "Are you sure you want to delete this bot for the {{ baseTicker }}-{{ quoteTicker }} market on {{ host }}?"},
idAllocationFormTitle: {T: "Allocate funds for {{ baseSymbol }} - {{ quoteSymbol }} on {{ host }}"},
}

var ptBR = map[string]*intl.Translation{
Expand Down
1 change: 0 additions & 1 deletion client/webserver/locales/ar.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ var Ar = map[string]*intl.Translation{
"Immature tickets": {T: "التذاكر غير الناضجة"},
"app_pw_reg": {Version: 1, T: "أدخل كلمة مرور التطبيق لتأكيد تسجيل منصة المبادلات اللامركزية DEX وإنشاء السندات."},
"treasury spends": {T: "نفقات الخزينة"},
"bots_running_view_only": {T: "البوتات قيد التشغيل. أنت في وضع العرض فقط."},
"buy_placements_tooltip": {T: "تحديد مواضع الشراء للبوت. سيقوم البوت بوضع الطلبات حسب ترتيب الأولوية إذا لم يكن الرصيد كافيًا لتقديم جميع الطلبات."},
"no_limit_bullet": {T: "ليس هناك حد لعدد إنشاء اللوت"},
"Error": {T: "خطأ"},
Expand Down
2 changes: 1 addition & 1 deletion client/webserver/locales/en-us.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,6 @@ var EnUS = map[string]*intl.Translation{
"decred_privacy": {T: "Decred's form of privacy is especially powerful because Decred wallets integrate privacy with staking, which facilitates a consistently large anonymity set, a critical feature for privacy."},
"privacy_optional": {T: "Privacy is completely optional, and can be disabled at any time. There are increased transaction fees associated with privacy, but these fees have historically been relatively negligible."},
"privacy_unlocked": {T: "The wallet must remain unlocked while mixing."},
"bots_running_view_only": {T: "Bots are running. You are in view-only mode."},
"select_a_cex_prompt": {T: "Select an exchange to enable arbitrage"},
"Market not available": {T: "Market not available"},
"bot_profit_title": {T: "Choose your profit threshold"},
Expand Down Expand Up @@ -679,4 +678,5 @@ var EnUS = map[string]*intl.Translation{
"Wallet Balances": {T: "Wallet Balances"},
"Placements": {T: "Placements"},
"delete_bot": {T: "Delete Bot"},
"bot_running": {T: "This bot is currently running"},
}
1 change: 0 additions & 1 deletion client/webserver/locales/pl-pl.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,6 @@ var PlPL = map[string]*intl.Translation{
"privacy_intro": {T: "Po włączeniu prywatności wszystkie środki będą wysyłane za pośrednictwem usługi maskowania historii adresów przy użyciu protokołu o nazwie StakeShuffle."},
"decred_privacy": {T: "Forma prywatności Decred jest szczególnie potężna, ponieważ portfele Decred integrują prywatność ze stakingiem, co daje ciągły dostęp do dużego zbioru anonimowości będącego kluczową cechą prywatności."},
"privacy_optional": {T: "Prywatność jest całkowicie opcjonalna i można ją wyłączyć w dowolnym momencie. Z prywatnością wiążą się zwiększone opłaty transakcyjne, ale w przeszłości były one stosunkowo niewielkie."},
"bots_running_view_only": {T: "Boty są uruchomione. Jesteś w trybie tylko do podglądu."},
"select_a_cex_prompt": {T: "Wybierz giełdę, aby uruchomić arbitraż"},
"Market not available": {T: "Rynek niedostępny"},
"bot_profit_title": {T: "Wybierz próg zysku"},
Expand Down
Loading
Loading