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

client/multi: Refactor MM Balance Handling #2641

Merged
merged 7 commits into from
Feb 19, 2024

Conversation

martonp
Copy link
Collaborator

@martonp martonp commented Dec 26, 2023

This PR makes market making dependent on the WalletHistorian interface, which will need to be implemented for all wallets. Currently only BTC/clone SPV wallets and ETH/Polygon wallets can be used.

client/{asset,core,rpcserver}: Add WalletTransaction

- This adds a WalletTransaction function to the WalletHistorian interface.
It returns information about a transaction which the wallet made, or one
in which the wallet received funds. The TransactionConfirmations function
is removed, as it will no longer be needed once all wallets support
WalletTransaction.

- A `PartOfActiveBalance` field is added to the `WalletTransaction` type.
This is required for market making to know when it is safe to consider the
funds from an incoming transaction available for use.

- A `ParentAssetLockedAmt` field is added to the `core.Order` type. This is
needed in order to know the amount of token's fee asset is locked for an
order.
client/mm: Refactor bot balance tracking

- This diff updates the balance tracking for market making bots. Previously,
there was a fragile diff based accounting technique. Now, on each bot
action, including trades, withdrawals, and deposits, a pending action is
stored. This pending action is updated until it is complete, at which time
it is removed, and the "base" balance of the bot is updated. The bot
balances are calculated by adding the base balances of the bot to the
effects of all pending actions.

- The `wrappedCore` and `wrappedCEX` types are removed, and their functionalities
are combined into a `unifiedExchangeAdaptor`. This is useful because
pending deposits and pending withdrawals have balance effects on both
CEX and DEX balances. Each `unifiedExchangeAdaptor` listens to a core
notification stream, and the `MarketMaker` type no longer needs to. In
a future refactor, the bots themselves will no longer listen to the
core notification functionality, and all common functionalities can be
moved to the `unifiedExchangeAdaptor`.

- Previously, the fee assets of tokens were not taken into account, but
now they are.

- The Binance library now uses a different endpoint, `/api/v3/balances`,
to get balances. This is supported by the Binance testnet api, so
testbinance is no longer required for balances.

@martonp martonp force-pushed the refactorMMBalanceHandling branch 2 times, most recently from c503c16 to 2410dbe Compare December 28, 2023 18:22
Copy link
Member

@JoeGruffins JoeGruffins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just running the basic bot without arb. Do not need to fix now but see these errors with cancel orders pop up:

2024-01-02 05:17:00.021 [ERR] MM[MarketMaker-127.0.0.1:17273-42-0]: Error canceling order 9d70534d61d934e097e00c37ce8b0959b5b3b2cded5947b6c4bbf049fc549149: order 9d70534d61d934e097e00c37ce8b0959b5b3b2cded5947b6c4bbf049fc549149 - only one cancel order can be submitted per order per epoch. still waiting on cancel order 068a0d0b27a7a8c666f68f7c7da84f54374034fb40fed3b2dda6e1dcdeec284c to match
2024-01-02 05:44:00.036 [ERR] MM[MarketMaker-127.0.0.1:17273-42-0]: Error canceling order 4b9b95ee7d8b02899a220d3a5547be362ab35c6706f7543b319ea3cae4f4ea8f: order 4b9b95ee7d8b02899a220d3a5547be362ab35c6706f7543b319ea3cae4f4ea8f not cancellable in status canceled

client/asset/eth/eth.go Outdated Show resolved Hide resolved
client/asset/eth/eth.go Outdated Show resolved Hide resolved
client/asset/interface.go Outdated Show resolved Hide resolved
client/asset/interface.go Outdated Show resolved Hide resolved
client/mm/exchange_adaptor.go Show resolved Hide resolved
client/mm/exchange_adaptor.go Outdated Show resolved Hide resolved
client/mm/exchange_adaptor.go Outdated Show resolved Hide resolved
client/asset/btc/btc.go Outdated Show resolved Hide resolved
client/asset/interface.go Outdated Show resolved Hide resolved
client/core/wallet.go Outdated Show resolved Hide resolved
client/core/wallet.go Outdated Show resolved Hide resolved
client/mm/exchange_adaptor.go Outdated Show resolved Hide resolved
client/mm/exchange_adaptor.go Outdated Show resolved Hide resolved
client/mm/exchange_adaptor_test.go Outdated Show resolved Hide resolved
Comment on lines +429 to +441
// setBalances queries binance for the user's balances and stores them in the
// balances map.
func (bnc *binance) setBalances(ctx context.Context) error {
var resp struct {
Balances []struct {
Asset string `json:"asset"`
Free float64 `json:"free,string"`
Locked float64 `json:"locked,string"`
} `json:"balances"`
}
err := bnc.getAPI(ctx, "/api/v3/account", nil, true, true, &resp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I made some changes in #2615 that might be relevant to these changes too. I think we should just expect to be connected to configured cexes, or connect on-the-fly when we need info.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change just updates the API used to get the balances. I think your changes over there are still good.

I think we should just expect to be connected to configured cexes, or connect on-the-fly when we need info.

I think the use should make some action before connecting the the CEX. It shouldn't automatically connect at start-up even if the user doesn't intend to use market making at all. They may want to set up some configurations before connecting.

@martonp martonp force-pushed the refactorMMBalanceHandling branch 2 times, most recently from 3dc39bc to 656f88a Compare January 16, 2024 16:37
return nil, err
}
if *tx.To() != w.netToken.Address {
w.log.Infof("from token address")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem very useful.

@@ -110,6 +113,10 @@ func (c *tokenFundingCoin) Value() uint64 {
return c.amt
}

func (c *tokenFundingCoin) ParentValue() uint64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just Fees?

// Returning configs for eth, btc, ltc, bch, zec, usdc, matic.
// Balances do not use a sapi endpoint, so they do not need to be handled
// here.
resp := `[
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resp := []byte(`[

// NOTE: This amount only applies to the wallet from which swaps are sent. This
// is the BASE asset wallet for a SELL order and the QUOTE asset wallet for a
// BUY order.
// lockedAmount should be called with the mtx >= RLocked.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lockedAmount -> parentLockedAmt

Comment on lines 526 to 537
pendingDeposit.mtx.RLock()
if pendingDeposit.assetID == assetID {
totalAvailableDiff -= int64(pendingDeposit.amtSent)
}

token := asset.TokenInfo(pendingDeposit.assetID)
if token != nil && token.ParentID == assetID {
totalAvailableDiff -= int64(pendingDeposit.fee)
} else if token == nil && pendingDeposit.assetID == assetID {
totalAvailableDiff -= int64(pendingDeposit.fee)
}
pendingDeposit.mtx.RUnlock()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fee is the only protected field, so you might as well

pendingDeposit.mtx.RLock()
fee := int64(pendingDeposit.fee)
pendingDeposit.mtx.RUnlock()

u.balancesMtx.Lock()
defer u.balancesMtx.Unlock()

if trade.Complete {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine, but the Trade returned from (CEX).Trade is never Complete.

Comment on lines 1140 to 1146
u.balancesMtx.RLock()
pendingOrder, found := u.pendingDEXOrders[orderID]
if !found {
u.balancesMtx.RUnlock()
return
}
u.balancesMtx.RUnlock()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u.balancesMtx.RLock()
pendingOrder, found := u.pendingDEXOrders[orderID]
u.balancesMtx.RUnlock()
if !found {
	return
}

Comment on lines 1154 to 1173
for txID := range swaps {
if _, found := pendingOrder.swaps[txID]; !found {
pendingOrder.swaps[txID] = &asset.WalletTransaction{}
}
}
for txID := range redeems {
if _, found := pendingOrder.redeems[txID]; !found {
pendingOrder.redeems[txID] = &asset.WalletTransaction{}
}
}
for txID := range refunds {
if _, found := pendingOrder.refunds[txID]; !found {
pendingOrder.refunds[txID] = &asset.WalletTransaction{}
}
}

// Query the wallet regarding all unconfirmed transactions
for txID, oldTx := range pendingOrder.swaps {
if oldTx.Confirmed {
continue
}
tx, err := u.clientCore.WalletTransaction(fromAsset, txID)
if err != nil {
u.log.Errorf("Error getting swap tx %s: %v", txID, err)
continue
}
pendingOrder.swaps[txID] = tx
}
for txID, oldTx := range pendingOrder.redeems {
if oldTx.Confirmed {
continue
}
tx, err := u.clientCore.WalletTransaction(toAsset, txID)
if err != nil {
u.log.Errorf("Error getting redeem tx %s: %v", txID, err)
continue
}
pendingOrder.redeems[txID] = tx
}
for txID, oldTx := range pendingOrder.refunds {
if oldTx.Confirmed {
continue
}
tx, err := u.clientCore.WalletTransaction(fromAsset, txID)
if err != nil {
u.log.Errorf("Error getting refund tx %s: %v", txID, err)
continue
}
pendingOrder.refunds[txID] = tx
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

processTxs := func(assetID uint32, m map[string]*asset.WalletTransaction, txs map[string]bool) {
	// Add new txs to tx cache
	for txID := range txs {
		if _, found := m[txID]; !found {
			m[txID] = &asset.WalletTransaction{}
		}
	}
	// Query the wallet regarding all unconfirmed transactions
	for txID, oldTx := range m {
		if oldTx.Confirmed {
			continue
		}
		tx, err := u.clientCore.WalletTransaction(assetID, txID)
		if err != nil {
			u.log.Errorf("Error getting tx %s: %v", txID, err)
			continue
		}
		m[txID] = tx
	}
}
processTxs(fromAsset, pendingOrder.swaps, swaps)
processTxs(toAsset, pendingOrder.redeems, redeems)
processTxs(fromAsset, pendingOrder.refunds, refunds)

dex.BipIDSymbol(feeAsset), u.baseDexBalances[feeAsset], deposit.fee)
u.baseDexBalances[feeAsset] = 0
} else {
u.baseDexBalances[feeAsset] -= deposit.fee
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike other balances, we only subtract from the fee balance. This means a bot will run out of allocated fee funding at some point, even if they're have profits to compensate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the asset is not a token, then this is not a problem, they are not tracked separately. If it is a token, there's nothing we can do other than buying the fee asset on another market. I think for now it'll just require a manual intervention every once in a while.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The operator's choice with this scheme is a lose-lose. They can either 1) Allocate a lot of funding for fees, possibly preventing them from making other markets, or they can 2) allocate a smaller amount, and risk their bot going down when things get hot, missing out on profit.

The bot should just ensure some minimum level of token fee funding during startup and periodically check the status of fee funding balances so that it can issue a notification is funding is low.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just roll with this for now. Will revisit later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bot should just ensure some minimum level of token fee funding during startup and periodically check the status of fee funding balances so that it can issue a notification is funding is low.

Yeah it can definitely do a notification. Other than that it can do some sort of reallocations between bots.

{
"coin": "MATIC",
"depositAllEnable": true,
"free": "0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not setting a balance?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we're not using this request to get the balances anymore. Using /api/v3/account which is supported by the Binance testnet.

@martonp martonp force-pushed the refactorMMBalanceHandling branch from 656f88a to 72a4835 Compare January 30, 2024 16:44
@buck54321
Copy link
Member

Rebase please.

@martonp martonp force-pushed the refactorMMBalanceHandling branch from 72a4835 to 55e2dd9 Compare February 2, 2024 18:01
- This adds a WalletTransaction function to the WalletHistorian interface.
It returns information about a transaction which the wallet made, or one
in which the wallet received funds. The TransactionConfirmations function
is removed, as it will no longer be needed once all wallets support
WalletTransaction.

- A `PartOfActiveBalance` field is added to the `WalletTransaction` type.
This is required for market making to know when it is safe to consider the
funds from an incoming transaction available for use.

- A `ParentAssetLockedAmt` field is added to the `core.Order` type. This is
needed in order to know the amount of token's fee asset is locked for an
order.
- This diff updates the balance tracking for market making bots. Previously,
there was a fragile diff based accounting technique. Now, on each bot
action, including trades, withdrawals, and deposits, a pending action is
stored. This pending action is updated until it is complete, at which time
it is removed, and the "base" balance of the bot is updated. The bot
balances are calculated by adding the base balances of the bot to the
effects of all pending actions.

- The `wrappedCore` and `wrappedCEX` types are removed, and their functionalities
are combined into a `unifiedExchangeAdaptor`. This is useful because
pending deposits and pending withdrawals have balance effects on both
CEX and DEX balances. Each `unifiedExchangeAdaptor` listens to a core
notification stream, and the `MarketMaker` type no longer needs to. In
a future refactor, the bots themselves will no longer listen to the
core notification functionality, and all common functionalities can be
moved to the `unifiedExchangeAdaptor`.

- Previously, the fee assets of tokens were not taken into account, but
now they are.

- The Binance library now uses a different endpoint, `/api/v3/balances`,
to get balances. This is supported by the Binance testnet api, so
testbinance is no longer required for balances.
@martonp martonp force-pushed the refactorMMBalanceHandling branch from 55e2dd9 to 2b1d2b1 Compare February 16, 2024 19:24
@buck54321 buck54321 merged commit 72a51e6 into decred:master Feb 19, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants