Skip to content

Conversation

buck54321
Copy link
Member

@buck54321 buck54321 commented Jul 27, 2022

core:
Add a market-maker bot to **core**. The bot uses some internal and
(optionally) external signals to calculate a ideal buy and sell
price and a "break-even spread", which is the spread at which
a buy-sell sequence's tx fees equal its profit. These values can
be used as inputs into one of five "gap strategies", which determine
the target spread.

The bot can be created, started, updated, paused, and retired. Each
of those actions generates a notification.

db:
New BotProgram type is not specific to market-maker bots. Everything
is structured with a mind towards future expansion with new automated
trading routines.

ui:
New /mm view enables creation, re-configuration, and monitoring of
existing market-maker bots.

@buck54321
Copy link
Member Author

buck54321 commented Jul 27, 2022

image

@buck54321
Copy link
Member Author

I've made the simnet test into a full-fledged cli bot. This means I could've foregone the simharness package move, but I do plan to use that package other places, so I'd love to keep that change.

@buck54321 buck54321 marked this pull request as ready for review August 13, 2022 16:18
@chappjc
Copy link
Member

chappjc commented Aug 13, 2022

I've made the simnet test into a full-fledged cli bot.

Glad to see it.

This means I could've foregone the simharness package move, but I do plan to use that package other places, so I'd love to keep that change.

In that case, will you please put the simharness.go creation and the loadbot changes in a separate commit? There's no clear connection to the mm bot work.

@@ -385,7 +385,7 @@ func (dc *dexConnection) bookie(marketID string) *bookie {
// syncBook subscribes to the order book and returns the book and a BookFeed to
// receive order book updates. The BookFeed must be Close()d when it is no
// longer in use. Use stopBook to unsubscribed and clean up the feed.
func (dc *dexConnection) syncBook(base, quote uint32) (BookFeed, error) {
func (dc *dexConnection) syncBook(base, quote uint32) (*orderbook.OrderBook, BookFeed, error) {
Copy link
Member

Choose a reason for hiding this comment

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

This is funny because originally we have the *OrderBook returned, then we removed it and had the orderbook snap as the first send on the feed (FreshBookAction I believe). It is certainly most convenient this way.

Comment on lines +594 to +612
func basisPrice(host string, mkt *Market, oracleBias, oracleWeighting float64, midGap uint64, bp basisPricer, log dex.Logger) uint64 {
basisPrice := float64(midGap) // float64 message-rate units

log.Tracef("basisPrice: mid-gap price = %d", midGap)

Copy link
Member Author

Choose a reason for hiding this comment

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

We should be a little more careful with using the current mid-gap here. If the book is sparse, someone could manipulate the bot into providing a favorable price by placing orders far from the gap. We should try to get some metric of order book health before using the mid-gap as an input, and we should probably only allow the mid-gap to vary so much from the oracle rate.

@martonp
Copy link
Collaborator

martonp commented Aug 14, 2022

Looks really cool. I'm just starting to look through, but I found an issue.. if you press enter instead of clicking the submit button when creating a bot, it will create two bots instead of just one. If you click the button, only one button is created.

Also, I created a bot, and I have funds in both wallets, but it is only creating orders on the buy side, not the sell side. Isn't it supposed to create for both?

Screen Shot 2022-08-14 at 12 18 52 PM
Screen Shot 2022-08-14 at 12 19 03 PM

Also, there should be a header on that table with the CEX info. And a K after the volume values.

@buck54321 buck54321 force-pushed the mmbot branch 4 times, most recently from 5cfb98e to 4bb7527 Compare August 15, 2022 02:21
@buck54321
Copy link
Member Author

if you press enter instead of clicking the submit button when creating a bot, it will create two bots instead of just one.

When you say "press enter", do you mean in the lot number input? I've tried on Chrome and Firefox, and can't seem to repro, which makes sense because it's not part of a form and there's no handler on that input to submit.

Maybe I've misunderstood. Please verify.

but it is only creating orders on the buy side

Not really sure about this, but I squashed some minor bugs that may have contributed. If you still see it, please run with
--log=BOT=trace,CORE=trace
and see if anything seems off.

@martonp
Copy link
Collaborator

martonp commented Aug 15, 2022

When you say "press enter", do you mean in the lot number input? I've tried on Chrome and Firefox, and can't seem to repro, which makes sense because it's not part of a form and there's no handler on that input to submit.

I meant press enter when submitting the password.

I'll check about the other issue later.

Copy link
Member

@chappjc chappjc left a comment

Choose a reason for hiding this comment

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

A first pass.

The new core/simharness package looks like a cool reusability refactor of some of the code in loadbot and elsewhere, but I don't see how it's related to this PR. Shouldn't it be delayed for when we're ready to put it into action in loadbot and eth/node_harness_test.go?

The cmd/mmbot app looks fine. I'm fairly certain that in the near future we'll want to hack in some paths for automated init/wallet-creation/register to suit existing workflows.
Also, it looks like it won't be a problem to start multiple bot programs within the one app, but if I'm not seeing an possible issue with that, let me know.

var (
userDir, _ = os.UserHomeDir()
defaultConfigFileName = "mm.json"
defaultCoreDir = filepath.Join(userDir, ".dexc")
Copy link
Member

@chappjc chappjc Sep 14, 2022

Choose a reason for hiding this comment

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

Why not dcrutil.AppData? Using /.dexc isn't going to jive with Windows and probably macos

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.

I was able to get banned on btc/dcr on simnet because the btc native wallet was too slow seemingly and missed a lot of redemptions. But anyway, still says running after I was banned.

image

The Oracle Bias slider is visible when weight is zero if you move the bar on another bot and then go to a bot with zero. Probably unintended.

image

Working well and looks good to me.

signal.Notify(killChan, os.Interrupt)
go func() {
for range killChan {
if c != nil && pgmID > 0 {
Copy link
Member

Choose a reason for hiding this comment

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

These two checks look like a race.

return strings.ToLower(strings.ReplaceAll(slug, " ", "-"))
}

func getInto(ctx context.Context, url string, thing interface{}) error {
Copy link
Member

Choose a reason for hiding this comment

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

Is this method name correct as get into?

@@ -1563,15 +1577,9 @@ func (btc *baseWallet) PreSwap(req *asset.PreSwapForm) (*asset.PreSwap, error) {
}
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated to these changes but there is an odd fmt.Printf that should maybe be a log.

btc.go:1397:            fmt.Printf("btc.apiFeeFallback: %+v\n\n", btc.apiFeeFallback())

Comment on lines +60 to +66
GapStrategyAbsolutePlus GapStrategy = "absolute-plus"
// GapStrategyPercent sets the spread as a ratio of the mid-gap rate.
// 0 <= r <= 0.1
GapStrategyPercent GapStrategy = "percent"
// GapStrategyPercentPlus sets the spread as a ratio of the mid-gap rate
// plus the break-even gap.
GapStrategyPercentPlus GapStrategy = "percent-plus"
Copy link
Member

Choose a reason for hiding this comment

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

I don't have a great grasp on what is needed here, but seems like Plus categories could just be an option and don't need separate strategies. Also, why add the break-even? Could set to break even if gap is lower also work?

Asking for my understanding.

Copy link
Member Author

Choose a reason for hiding this comment

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

Gaps above the break-even are "profitable". A gap below the break-even guarantees losses. Having break-even as a minimum allows the bot to adapt to changing market conditions, particularly changes in tx fee rates, without user input.

Copy link
Member Author

Choose a reason for hiding this comment

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

Could set to break even if gap is lower also work?

I'm not sure I understand this question.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I understand this question.

If break even is .07 and the program would set to .1 adding sets it to .17 but really .1 was already enough to bring it above.

@buck54321 buck54321 force-pushed the mmbot branch 2 times, most recently from 1dc4ad5 to 903dcca Compare September 22, 2022 14:56
@buck54321
Copy link
Member Author

I thought I had deleted the simharness stuff already, but I guess I never git rm'd. It's gone now.

@buck54321
Copy link
Member Author

The cmd/mmbot app looks fine. I'm fairly certain that in the near future we'll want to hack in some paths for automated init/wallet-creation/register to suit existing workflows.
Also, it looks like it won't be a problem to start multiple bot programs within the one app, but if I'm not seeing an possible issue with that, let me know.

I was actually kinda hoping you would iterate on the cli app after this. Here are the three big enhancements I'd like to see going forward.

  1. CLI App
  2. Inventory management
  3. Arbitrage

all of which I think deserve their own dedicated efforts.

@chappjc
Copy link
Member

chappjc commented Sep 22, 2022

I was actually kinda hoping you would iterate on the cli app after this.

Gladly.

Here are the three big enhancements I'd like to see going forward.

Yes, no question. We need these, particularly arb.

Copy link
Collaborator

@martonp martonp left a comment

Choose a reason for hiding this comment

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

Looking really nice.

A few UI issues:

  • After pausing a bot, it still shows running
  • The settings icon on the top right corner is not visible when starting the app. If I type /mm into the address bar, the settings icon shows up.
  • The drift tolerance minimum is actually 0.1%, but you can enter 0 in the option selector.


// validateProgram checks the sensibility of a *MakerProgram's values and sets
// some defaults.
func validateProgram(pgm *MakerProgram) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

EmptyMarketRate is not checked in validateProgram.

Copy link
Member Author

Choose a reason for hiding this comment

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

We just can't know here whether it's required or not. For many or most markets, it probably isn't needed.

// 5. Calculate how many lots are needed to be ordered in order to meet the
// 2 x L commitment. If low balance restricts the maintenance of L lots on
// one side, allow the difference in lots to be added to the opposite side.
// 6. Place orders, cancels first, then buys and sells.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does the order that they are placed matter if all of the orders are shuffled while matching?

Copy link
Member Author

Choose a reason for hiding this comment

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

This was more about just mapping out the bot game plan. You're correct that we can't control the matching sequence of orders placed in the same epoch.

log dex.Logger
book *orderbook.OrderBook

running uint32
Copy link
Collaborator

Choose a reason for hiding this comment

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

// atomic

wg sync.WaitGroup
die context.CancelFunc

rebalanceRunning uint32
Copy link
Collaborator

Choose a reason for hiding this comment

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

// atomic

basisPrice = msgOracleRate
log.Tracef("basisPrice: using basis price %.0f from oracle because no mid-gap was found in order book", basisPrice)
} else if oracleWeighting > 0 {
basisPrice = msgOracleRate*oracleWeighting + basisPrice*(1-oracleWeighting)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the oracle price is completely incorrect due to some bug with the oracle, the basisPrice would be way off here as well, potentially creating some orders that would lose a lot of money. Maybe there should be a setting to not place any orders in case of a big difference between the mid-gap and the oracle price? Also it would be good to exclude outlier oracle prices.

What is the reason for using an oracle price other than making sure that the order book is not being manipulated? If this bot isn't being used to do arbitrage with another exchange, why should the price that is used by the bot be adjusted based on some external price?

Copy link
Member Author

@buck54321 buck54321 Oct 18, 2022

Choose a reason for hiding this comment

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

What is the reason for using an oracle price other than making sure that the order book is not being manipulated?

For a low-depth book, it would be super easy to drain a bot at a favorable price by manipulating the mid-gap rate.

Copy link
Collaborator

Choose a reason for hiding this comment

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

So the oracle weight makes it harder to do this type of manipulation, but if someone is able to do a major manipulation, then even a 50% weight wouldn't save the victim bots. I think it would make sense to have the oracle weight combined with another setting that says if the difference between the mid-gap rate and the oracle rate is greater than some percentage, then don't place any orders.

Copy link
Member Author

@buck54321 buck54321 Oct 18, 2022

Choose a reason for hiding this comment

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

I think I've captured at least part of what you're asking with maxOracleMismatch, but I'm not disallowing ordering, because that could basically brick the bot e.g. if the book is empty expect for a single order way off-market. Instead, I'm setting a limit to the difference, and using using an adjusted mid-gap if the difference is too large. In practice, a user should use a very high if not 100% oracle weighting when the book is spOracleBiasarse, but this feature should protect the user regardless.


Some other features that I think would be good for followup.

  1. Using 24-hour volume for price-weighting is kinda hacky. We could improve it by, for example, creating a metric for book depth near market price and using that value instead or mixing it in.
  2. Outlier filtering, as you mentioned.
  3. Automatic oracle weighting based on which markets are busy and which are dead. If DEX is super-busy, we need less oracle weighting.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I've captured at least part of what you're asking with maxOracleMismatch

Ah yeah that's right.

Another feature could be for the bot to turn off if it keeps losing money. I'd definitely have more peace of mind to leave it on after going to sleep like that.

var halfSpread uint64
switch pgm.GapStrategy {
case GapStrategyMultiplier:
halfSpread = uint64(math.Round(float64(breakEven) * (1 + pgm.GapFactor)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this 1 + pgm.GapFactor? It seems correct if it is exactly break even when the GapFactor is 1.

Copy link
Collaborator

@martonp martonp left a comment

Choose a reason for hiding this comment

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

I think it's looking really good. I see no issues when the bot is trading against the loadbot.

I think it would be nice to have a report of all of a bot's actions and how it performed. Also, something like the loadbot that simulates different market conditions would be cool.

In a buy\textrightarrow sell sequence with a positive gap, we end up with more of the quote asset and less of the base asset. Setting our loss ratio equal to our profit ratio, we get

\[
\frac{F_B}{L} = \frac{p_sL - p_bL - F_Q}{p_bL}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nbd, but it seems more intuitive to start with (Fb * pb) + Fq = psL - pbL, saying the fees are equal to the profits.

} else {
m.log.Errorf("Bot MaxBuy error: %v", err)
}
maxSell, err := c.MaxSell(pgm.Host, pgm.BaseID, pgm.QuoteID)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What if while a bot is being created, another bot has not yet placed its orders? Then maybe there won't be enough funds for both bots to place all the orders they need.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this should be part of a larger effort to handle "inventory management". Essentially, bots need to have have limits to how much funds they can access. I think Core will unfortunately need to be involved and it might be a pita.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah I was thinking the same thing, definitely sounds like a pita.

type Spreader func(ctx context.Context, baseSymbol, quoteSymbol string) (sell, buy float64, err error)

var spreaders = map[string]Spreader{
"binance.com": fetchBinanceSpread,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we add OKEX and gate.io? They are no.2 and no.3 for DCR volume.

Copy link
Member

Choose a reason for hiding this comment

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

Pls skip gate, not hi fidelity numbers.

Copy link
Member Author

@buck54321 buck54321 Nov 2, 2022

Choose a reason for hiding this comment

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

Not adding any more here, but can do after.

core:
Add a market-maker bot to **core**. The bot uses some internal and
(optionally) external signals to calculate a ideal buy and sell
price and a "break-even spread", which is the spread at which
a buy-sell sequence's tx fees equal its profit. These values can
be used as inputs into one of five "gap strategies", which determine
the target spread.

The bot can be created, started, updated, paused, and retired. Each
of those actions generates a notification.

db:
New BotProgram type is not specific to market-maker bots. Everything
is structured with a mind towards future expansion with new automated
trading routines.

ui:
New /mm view enables creation, re-configuration, and monitoring of
existing market-maker bots.
@chappjc chappjc merged commit 7d3e6c0 into decred:master Nov 2, 2022
@chappjc chappjc added the bot label Mar 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants