Ticks, ratios, and prices
On Mangrove, the price space of a market is discretized into ticks: It contains the price 1 quote/base
and the smallest price increment (and decrement) is 1 basis point = 0.01%.
Formally, a corresponds to the ratio between the amount promised by an offer and the amount it requests. This corresponds to a price in the following way:
As always when ERC-20 tokens are involved, careful handling of decimals is essential! Both in code and in documentation.
In the formulae on this page, we only consider raw values, i.e. token decimals are disregarded. On-chain, Mangrove does the same.
As an important example of the implications, note that while the price space contains the raw price 1, it may not contain the user readable price 1. This is likely if base and quote have different numbers of decimals.
The next section discusses this in detail and explains how to handle decimals, including how to convert raw ratios and raw prices to their user representations.
Note that the relationship between ratio and price depends on which side of the book you are looking at:
On the asks side, it's fairly natural: The price is the ratio between the amount of base tokens promised by an offer and the amount of quote tokens it requests.
On the bids side, however, the price is the reciprocal of the ratio, i.e. price = . This is because a bid wants base tokens and offers quote tokens and thus the ratio is in
base/quote
.
On a WETH/DAI market:
- The price would be expressed in DAI per WETH (DAI/WETH)
- Eg. 2,224 DAI/WETH
- On the asks side WETH-DAI (selling WETH or buying DAI), the ratio equals the price and has unit DAI/WETH
- Eg. 2,224 DAI/WETH
- On the bids side DAI-WETH (buying WETH or selling DAI), the ratio equals 1/price and has unit WETH/DAI.
- Eg. 0.0004496 WETH/DAI
We often casually use the word βpriceβ to refer to the ratio between the amount promised by an offer and the amount it requests. In the code however, we use the generic word βratioβ to avoid confusion with the normal notion of market price expressed in βquoteβ tokens per βbaseβ token.
Decimals, raw values, and user representationsβ
On-chain, ERC-20 amounts are integers. But when presented to users, ERC-20 tokens typically have a number of decimals, such that fractional amounts can be expressed.
The number of decimals is specified by the ERC-20 contract and differs between different ERC-20 tokens.
Here are some ERC-20 tokens and example amounts in both the on-chain (so-called 'raw') format and their user representation.
ERC-20 | Decimals | Raw amount example | User representation |
---|---|---|---|
USDC | 6 | 87654321 | 87.654321 |
DAI | 18 | 876543210987654321 | 0.876543210987654321 |
WETH | 18 | 109876543210987654321 | 109.876543210987654321 |
When tokens have different numbers of decimals, their raw amounts are not easily relatable. The USD stable coins USDC and DAI are a good example illustrated in the example above: The amounts are in principle directly relatable as they denote a USD amount. But it's not obvious from the raw amounts, which of them represents the larger USD amount.
It becomes even more tricky on markets, where we need to express ratios and prices. When the base and quote tokens have different numbers of decimals, raw ratios and raw prices become non-intuitive.
Here are some examples of what this can look like:
Market | Decimals | Ask user ratio example (quote/base) | Ask ratio (raw) | Bid user ratio example (base/quote) | Bid ratio (raw) |
---|---|---|---|---|---|
DAI/USDC | DAI: 18 USDC: 6 | | | ||
WETH/DAI | WETH: 18 DAI: 18 | | |
On-chain, Mangrove only works with raw token amounts and ratios between them. And the relationsships between ticks, ratios, and prices described on this page hold for raw token amounts.
The formulae for converting between raw ratios&prices and their user representations are the following:
Converting a price to a tickβ
Since the price space is discretized, not all prices and ratios can be represented exactly. Converting a price or ratio to a tick may therefore require rounding and one should take care to round appropriately; We'll discuss this in detail below.
First, calculate an exact but possibly non-representable tick'
which may have a real component:
Next, round this value to an integer to get a representable tick. One will typically want to round down or up depending on whether the price or ratio is used in a maker or taker context:
Role | Tick rounding | Explanation |
---|---|---|
Taker | Round down | Takers specify a max price when buying and min price when selling. In both cases, this price corresponds to a max ratio they're willing to accept. Rounding down the tick corresponds to choosing the tick closest to but not exceeding that ratio. This ensures the taker doesn't get a worse price than specified. |
Maker | Round up | Makers specify the price they're willing to accept. This price corresponds to a ratio as discussed above. Rounding up the tick corresponds to choosing the tick closest to but not below that ratio. This ensures the maker doesn't receive too little. |
tickSpacing
: Markets with bigger price incrementsβ
Mangrove allows markets to require bigger price increments than 0.01%. This is achieved through use of the tickSpacing
parameter which restricts the allowed ticks to multiples of tickSpacing
: Only ticks where tick % tickSpacing == 0
are valid on a market.
This means that the price increments are .
For a market with tickSpacing = 5
, the allowed ticks are ..., -15, -10, -5, 0, 5, 10, 15, ...
and the price increments are .
Tick, ratio, and price rangesβ
The supported ranges for ticks, ratios, and prices in Mangrove are:
Value | Min | Max |
---|---|---|
Tick | -887272 | 887272 |
Ratio | 2.9e-39 | 3.4e38 |
Price | 2.9e-39 | 3.4e38 |
The TickLib libraryβ
The TickLib
library contains utility functions for converting between ticks and ratios.
It also contains functions for calculations involving ticks and ratios, such as computing the inbound volume corresponding to a tick and outbound volume.
Comparison to Uniswap ticksβ
Mangrove's ticks are inspired by Uniswapβs tick, with the following notable differences:
- Directly computing ticks base 1.0001 (not base
sqrt(1.0001)
) - Directly computing ratios (not
sqrt(ratio)
- it makes the code simpler when dealing with actual ratios and logs of ratios) - Ratios are floating-point numbers, not fixed-point numbers (it increases the precision when computing amounts).