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

Indicate custom liquidity #34

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,32 @@ Example, here a delay of **60 seconds** is used:
}
```

There are 2 modes available with a 3rd on the way.

* `pricefeed`: Follows an external price oracle and updates indicated bids and asks based on that.
* `constant`: Sets an `initPrice` and market makes around that price. Can be combined with single-sided liquidity to simulate limit orders.
* `independent`: Under development. The price is set independent of a price feed.

For all modes the `slippageRate`, `maxSize`, `minSize`, `minSpread`, and `active` settings are mandatory.

For `pricefeed` mode, the `priceFeedPrimary` is mandatory.

For `independent` and `constant` mode, the `initPrice` is mandatory.

The `side` setting can be toggled for single-sided liquidity. By default, the side setting is set to `d`, which stands for double-sided liquidity. To toggle single-sided liquidity, the value can be set to `b` or `s` for buy-side only or sell-side only.

The primary price feed is the price feed used to determine the bids and asks of the market maker. The secondary price feed is used to validate the first price feed and make sure the market isn't returning bad data. If the primary and secondary price feeds vary by more than 1%, the market maker will not fill orders.

The slippage rate is the rate at which the spread increases as the base unit increases. For the example above, the spread goes up by 1e-5 for every 1 ETH in size added to an order. That's the equivalent of 0.1 bps / ETH in slippage.

Orders coming in below the `minSpread` from the price feed will not be filled. The spread is calculated as a decimal value. 0.01 is 1%, and 0.0002 is 2 basis points (bps).

A market can be set inactive by flipping the active switch to `false`.

`indicateLiquidity` can be used to set custom spread levels on the UI. By default 10 splits are used to split up the liquidity but this allows you to indicate something else besides that. Liquidity will still be filled based on the other settings, so this is for visual purposes only. It is usually omitted or left blank and most users shouldn't use it. An example with `indicateLiquidity` can be found in the Pair Setting Examples.

## Pair Options

###### increaseSpreadAfterFill
The market maker increases the spread by the set amount. After the time (**in seconds**) the spread will fall back to the old value. This can happen multiple times in case the mm fills again in the set time (e.g. 0.1 -> 0.2 -> 0.3).
Example:
Expand Down Expand Up @@ -223,6 +249,25 @@ Sell the rip:
}
```

Indicate non-standard liquidity.

```
"DYDX-USDC": {
"mode": "constant",
"side": "s",
"initPrice": 20,
"slippageRate": 1e-5,
"indicateLiquidity": [
[0.0001, 10],
[0.0003, 10],
]
"maxSize": 1000,
"minSize": 0.5,
"minSpread": 0,
"active": true
}
```

## Configuration Via Environment Variables

If your hosting service requires you to pass in configs via environment variables you can compress `config.json`:
Expand Down
40 changes: 29 additions & 11 deletions marketmaker.js
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) {

subscriptionMsg.subscribe.subscriptions.push({
"streamSubscription": {
"resource": `markets:${cryptowatchMarketId}:trades`
"resource": `markets:${cryptowatchMarketId}:book:spread`
}
})
}
Expand All @@ -571,8 +571,9 @@ async function cryptowatchWsSetup(cryptowatchMarketIds) {
if (!msg.marketUpdate) return;

const marketId = "cryptowatch:" + msg.marketUpdate.market.marketId;
let trades = msg.marketUpdate.tradesUpdate.trades;
let price = trades[trades.length - 1].priceStr / 1;
let ask = msg.marketUpdate.orderBookSpreadUpdate.ask.priceStr;
let bid = msg.marketUpdate.orderBookSpreadUpdate.bid.priceStr;
let price = ask / 2 + bid / 2;
PRICE_FEEDS[marketId] = price;
}
function onclose () {
Expand Down Expand Up @@ -644,16 +645,33 @@ function indicateLiquidity (pairs = MM_CONFIG.pairs) {
const maxSellSize = Math.min(baseBalance, mmConfig.maxSize);
const maxBuySize = Math.min(quoteBalance / midPrice, mmConfig.maxSize);

const splits = 10;
const liquidity = [];
for (let i=1; i <= splits; i++) {
const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/splits));
const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/splits));
if ((['b','d']).includes(side)) {
liquidity.push(["b", buyPrice, maxBuySize / splits, expires]);
if (mmConfig.indicateLiquidity) {
for (let i in mmConfig.indicateLiquidity) {
const entry = mmConfig.indicateLiquidity[i];
const spread = entry[0];
const amount = entry[1];
const buyPrice = midPrice * (1 - spread);
const sellPrice = midPrice * (1 + spread);
if ((['b','d']).includes(side)) {
liquidity.push(["b", buyPrice, amount, expires]);
}
if ((['s','d']).includes(side)) {
liquidity.push(["s", sellPrice, amount, expires]);
}
}
if ((['s','d']).includes(side)) {
liquidity.push(["s", sellPrice, maxSellSize / splits, expires]);
}
else {
const splits = 10;
for (let i=1; i <= splits; i++) {
const buyPrice = midPrice * (1 - mmConfig.minSpread - (mmConfig.slippageRate * maxBuySize * i/splits));
const sellPrice = midPrice * (1 + mmConfig.minSpread + (mmConfig.slippageRate * maxSellSize * i/splits));
if ((['b','d']).includes(side)) {
liquidity.push(["b", buyPrice, maxBuySize / splits, expires]);
}
if ((['s','d']).includes(side)) {
liquidity.push(["s", sellPrice, maxSellSize / splits, expires]);
}
}
}
const msg = { op: "indicateliq2", args: [CHAIN_ID, marketId, liquidity, CLIENT_ID] };
Expand Down