-
Notifications
You must be signed in to change notification settings - Fork 126
multi: implement a market-maker bot #1738
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
Conversation
59cdc17
to
b72b938
Compare
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. |
Glad to see it.
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) { |
There was a problem hiding this comment.
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.
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) | ||
|
There was a problem hiding this comment.
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.
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? Also, there should be a header on that table with the CEX info. And a K after the volume values. |
5cfb98e
to
4bb7527
Compare
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.
Not really sure about this, but I squashed some minor bugs that may have contributed. If you still see it, please run with |
I meant press enter when submitting the password. I'll check about the other issue later. |
There was a problem hiding this 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") |
There was a problem hiding this comment.
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
There was a problem hiding this 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.
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.
Working well and looks good to me.
client/cmd/mmbot/main.go
Outdated
signal.Notify(killChan, os.Interrupt) | ||
go func() { | ||
for range killChan { | ||
if c != nil && pgmID > 0 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) { | |||
} |
There was a problem hiding this comment.
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())
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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
1dc4ad5
to
903dcca
Compare
I thought I had deleted the simharness stuff already, but I guess I never |
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.
all of which I think deserve their own dedicated efforts. |
Gladly.
Yes, no question. We need these, particularly arb. |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
client/core/bot.go
Outdated
log dex.Logger | ||
book *orderbook.OrderBook | ||
|
||
running uint32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// atomic
client/core/bot.go
Outdated
wg sync.WaitGroup | ||
die context.CancelFunc | ||
|
||
rebalanceRunning uint32 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
- 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.
- Outlier filtering, as you mentioned.
- Automatic oracle weighting based on which markets are busy and which are dead. If DEX is super-busy, we need less oracle weighting.
There was a problem hiding this comment.
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.
client/core/bot.go
Outdated
var halfSpread uint64 | ||
switch pgm.GapStrategy { | ||
case GapStrategyMultiplier: | ||
halfSpread = uint64(math.Round(float64(breakEven) * (1 + pgm.GapFactor))) |
There was a problem hiding this comment.
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.
There was a problem hiding this 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} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.