Saturation
Authors: imi@1m1.io, Will duelingGalois@protonmail.com
Saturation, or sat, is defined as the net borrow. In theory, we would want to divide net borrow by the total liquidity; in practice, we keep the net borrow only in the tree. The unit of sat is relative to active liquidity assets, or the amount of L deposited less the amount borrowed. When we determine how much a swap moves the price, or square root price, we can define our equation using ticks, or tranches (25 ticks), where for some base , the square root price is for some tick . Alternatively for a larger base we can define the square root price as for some tranche . Using the square root price, we can define the amount of x or y in each tranche as:
where liquidity is . If we want to know how much debt of x or y can be liquidated within one tick, we can solve these equations for L and then the amount of x and y are considered the debt we would like to see if it could be liquidated in one tick. If saturation with respect to our starting is smaller, that amount of debt can be liquidated in one swap in the given ticks. Otherwise it is too big and can not. Note that we assume and . Then our definition of saturation relative to L is as follows,
Saturation is kept in a tree, starting with a root, levels and leafs. We keep 2 trees, one for net X borrows, another for net Y borrows. The price is always the price of Y in units of X. Mostly, the code works with the sqrt of price. A net X borrow refers to a position that if liquidated would cause the price to become smaller; the opposite for net Y positions. Ticks are along the price dimension and int16. Tranches are 25 ticks, stored as int16. Leafs (uint16) split the sat, which is uint112, into intervals. From left to right, the leafs of the tree cover the sat space in increasing order. Each account with a position has a price at which its LTV would reach LTVMAX, which is its liquidation (=liq) price. To place a debt into the appropriate tranche, we think of each debt and its respective collateral as a series of sums, where each item in the series fits in one tranche. Using formulas above, we determine the number of ticks a debt would cross if liquidated. This is considered the span of the liquidation. Using this value we then determine the start and end points of the liquidation, where the start would be closer to the prices, on the right of the end for net debt of x and on the left of the end for net debt of Y. Once we have the liquidation start, end, and span, we begin to place the debt, one tranche at a time moving towards the price. In this process we compare the prior recorded saturation and allow the insertion up to some max, set at 90% or the configuration set by the user. A Tranche contains multiple accounts and thus a total sat. The tranche's sat assigns it to a leaf. Each leaf can contain multiple tranches and thus has a total actual sat whilst representing a specific sat per tranche range. Leafs and thus tranches and thus accounts above a certain sat threshold are considered over saturated. These accounts are penalized for being in an over saturated tranche. Each account, tranche and leaf has a total penalty that needs to be repaid to close the position fully. Sat is distributed over multiple tranches, in case a single tranche does not have enough available sat left. Sat is kept cumulatively in the tree, meaning a node contains the sum of the sat of its parents. Updating a sat at the bottom of the tree requires updating all parents. Penalty is kept as a path sum, in uints of LAssets, meaning the penalty of an account is the sum of the penalties of all its parents. Updating the penalty for a range of leafs only requires updating the appropriate parent. Position (=pos) refers to the relative index of a child within its parent. Index refers to the index of a node in within its level The formula for allocating saturation is derived from,
for the start and end of liquidation and respectively. When we consider our buckets of TICKS_PER_TRANCHE we can rewrite this as a series where each boundary of each tranche where for a net debt of X and for a net debt of Y and for each subsequent tranche and . Thus we can rewrite the equations as:
We then define the left side of this equation as total saturation or newSaturation as we call it in the parameter passed in. Saturation is relative to the saturation in one tranche. The right side of the equation defines the saturation in each tranche , starting at the furthest point from the tranche and moving forward.
When calculating the case for Y, the result is almost identical, except our definition for requires us to multiply by rather than divide. The above shows the logic applied in this function. We can allocate saturation across each tranche until the total remaining saturation is depleted. We allow less than the ideal saturation to be consumed if there is less available. Extra saturation is then carried forward to tranches closer to the price, requiring part of the position to be liquidated sooner as needed based on the available liquidity. Two critical nuances of this algorithm is that we reduce by a factor of after each iteration and we multiply one time by after we allocate one time. The reduction of each iteration reflects the increase in the size of each tranche relative to a unit of X or Y as you move from one tranche to the next towards the price. The one time multiplication of is an adjustment for the offset of the start of liquidation relative to the start of the second tranche to minimize the impact of the reduction by since the first portion of saturation does not use an entire tranche. If saturation reaches the minOrMaxTick, we revert as the position is already reaching the limit of our probable price range and may require immediate liquidation if opened.
State Variables
SATURATION_TIME_BUFFER_IN_MAG2
time budget added to sat before adding it to the tree; compensates for the fact that the liq price moves closer to the current price over time.
uint256 internal constant SATURATION_TIME_BUFFER_IN_MAG2 = 101;
START_SATURATION_PENALTY_RATIO_IN_MAG2
percentage of max sat per tranche where penalization begins
uint256 internal constant START_SATURATION_PENALTY_RATIO_IN_MAG2 = 85;
MAX_INITIAL_SATURATION_MAG2
maximum initial saturation percentage when adding a new position
uint256 internal constant MAX_INITIAL_SATURATION_MAG2 = 90;
EXPECTED_SATURATION_LTV_MAG2_TIMES_SAT_BUFFER_SQUARED
, a constant used in calculations.
uint256 internal constant EXPECTED_SATURATION_LTV_MAG2_TIMES_SAT_BUFFER_SQUARED = 867_085;
EXPECTED_SATURATION_LTV_PLUS_ONE_MAG2
, a constant used in calculations.
uint256 internal constant EXPECTED_SATURATION_LTV_PLUS_ONE_MAG2 = 185;
SAT_RESET_FOR_STRADDLE_SLOPE_BIPS
Slope for calculating premium when resetting saturation for straddle positions where transitions to . The current value is 50 * BIPS which allows for the premium to grow quickly as the straddle's window of insolvency grows rapidly once they reach this transition point.
uint256 internal constant SAT_RESET_FOR_STRADDLE_SLOPE_BIPS = 500_000;
SAT_CHANGE_OF_BASE_Q128
a constant used to change the log base from the tick math base to the saturation to leaf base.
uint256 private constant SAT_CHANGE_OF_BASE_Q128 = 0xa39713406ef781154a9e682c2331a7c03;
SAT_CHANGE_OF_BASE_TIMES_SHIFT
a constant used to shift when changing the base from tick math base to the saturation leaf base.
uint256 private constant SAT_CHANGE_OF_BASE_TIMES_SHIFT = 0xb3f2fb93ad437464387b0c308d1d05537;
TICK_OFFSET
tick offset added to ensure leaf calculation starts from 0 at the lowest leaf
int16 private constant TICK_OFFSET = 1112;
LOWEST_POSSIBLE_IN_PENALTY
the lowest possible saturation is always in penalty
uint256 internal constant LOWEST_POSSIBLE_IN_PENALTY = 0xd9999999999999999999999999999999;
MIN_LIQ_TO_REACH_PENALTY
the minimum liquidity to reach the possibility of being in penalty.
uint256 private constant MIN_LIQ_TO_REACH_PENALTY = 850;
INT_ONE
Constant number one as an int type. Used for rounding or iterating direction.
int256 private constant INT_ONE = 1;
INT_NEGATIVE_ONE
Constant number negative one. Used for rounding or iterating direction.
int256 private constant INT_NEGATIVE_ONE = -1;
INT_ZERO
Constant number zero as an int type. Used for rounding or iterating direction.
int256 private constant INT_ZERO = 0;
LEVELS_WITHOUT_LEAFS
Tree leafs are on level LEVELS_WITHOUT_LEAFS; root is level 0
uint256 internal constant LEVELS_WITHOUT_LEAFS = 3;
LOWEST_LEVEL_INDEX
for convenience, since used a lot, ==LEVELS_WITHOUT_LEAFS - 1
uint256 internal constant LOWEST_LEVEL_INDEX = 2;
LEAFS
uint256 internal constant LEAFS = 4096;
CHILDREN_PER_NODE
uint256 internal constant CHILDREN_PER_NODE = 16;
CHILDREN_AT_THIRD_LEVEL
uint256 private constant CHILDREN_AT_THIRD_LEVEL = 256;
TICKS_PER_TRANCHE
is the base for ticks, then the tranche base is , int only to not need casting below, equals TICKS_PER_TRANCHE
int256 internal constant TICKS_PER_TRANCHE = 25;
TRANCHE_BASE_OVER_BASE_MINUS_ONE_Q72
for convenience, used to determine max sat per tranche to not cross in liq swap:
uint256 constant TRANCHE_BASE_OVER_BASE_MINUS_ONE_Q72 = 0x5a19b9039a07efd7b39;
MIN_TRANCHE
TickMath.MIN_TICK / TICKS_PER_TRANCHE - 1; // -1 to floor
int256 internal constant MIN_TRANCHE = -795;
MAX_TRANCHE
TickMath.MAX_TICK / TICKS_PER_TRANCHE;
int256 internal constant MAX_TRANCHE = 794;
FIELD_NODE_MASK
constants for bit reading and writing in nodes.
type(uint256).max >> (TOTAL_BITS - FIELD_BITS);
uint256 private constant FIELD_NODE_MASK = 0xffff;
SATURATION_MAX_BUFFER_TRANCHES
Buffer space (in tranches) allowed above the highest used tranche before hitting maxLeaf limit
uint8 internal constant SATURATION_MAX_BUFFER_TRANCHES = 3;
QUARTER_OF_MAG2
Twenty-five percent magnitude of two.
uint256 private constant QUARTER_OF_MAG2 = 25;
QUARTER_MINUS_ONE
Twenty-five percent minus one magnitude of two.
uint256 private constant QUARTER_MINUS_ONE = 24;
NUMBER_OF_QUARTERS
quarters per tranche.
uint256 private constant NUMBER_OF_QUARTERS = 4;
TWO_Q72
, used in saturation formula.
uint256 private constant TWO_Q72 = 0x2000000000000000000;
FOUR_Q144
, needed in quadratic formula is saturation.
uint256 private constant FOUR_Q144 = 0x4000000000000000000000000000000000000;
MAG4_TIMES_Q72
constant needed in formula.
uint256 private constant MAG4_TIMES_Q72 = 0x2710000000000000000000;