Rewards are available for market makers automatically
Rewards are based on a scoring system that considers each maker’s active limit orders every minute
Makers score quadratically more the closer they quote to the spread
Larger orders linearly increase scores
Quoting should be balanced because only the worst scoring side in a market contributes to score
A maker’s scoring relative to other makers matters
Uptime is effectively considered quadratically
Note: This article reflects the rewards program as of March 15th, 2023. The program is continuously evolved, and the official documentation should be referred to for an up-to-date reference of calculations. Notably, single-sided liquidity rewards were released with Epoch 56. To do this, a simple modification was made to equation 4.
In this article, we will give an example-based introduction to the Polymarket Market Making Reward Program. We will start by providing an overview of “binary trading” on Polymarket to develop foundation knowledge. Then, we will dive into the rewards program with the aim of helping the reader develop an understanding of the program and ultimately ideas of how they can profitably earn rewards by providing liquidity on Polymarket. If you aren’t familiar with Polymarket’s underlying outcome token primitive, we suggest first reading our recent blog post “A Detailed Look at Polymarket’s Binary Outcome Tokens”.
When you first begin trading on Polymarket, one thing you will notice is that when you place a limit order for an outcome (ie YES), a limit order also appears on the complementary outcome’s book (ie NO). The reason for this is that underlying binary markets is a single, “unified” book. When trading against this book, the view of the orders in it changes based on what outcome is being traded but the orders themselves are not mutated (and obviously can’t be changed because they are signed objects). The view change is a simple inversion of bids and asks (bids become asks and asks become bids) and the prices are adjusted to be 1 - the complement book’s order price. All directions of trading (ie buy YES, buy NO, sell YES, sell NO) are supported by orders on this single underlying book. A few simple examples will help illustrate exactly how this works.
Let’s consider the simplest case where both users are trading the same asset. Our first trader, Jack, places an order to buy 10 YES @$0.35. Our second trader, Jill, places an order to sell 10 YES @$0.35. The matching calculation for this example is simple, $3.50 will be transferred from Jack to Jill in exchange for her 10 shares.
Now, let’s consider the case where the users both want to buy a complementary asset. Our first trader Jack again places an order to buy 10 YES @$0.35. This time, our second trader, Jill, is actually interested in the NO side and thus places an order to buy 10 NO @$0.65. At first glance it might seem that these orders are distinct, but recall that $1 can be split to 1 YES and 1 NO at anytime, thus we can actually calculate a match here. The matching calculation is as follows transfer $3.50 from Jack and $6.50 from Jill ($10 in total) and use it to mint 10 full conditional token sets (10 YES and 10 NO), then distribute 10 YES to Jack and 10 NO to Jill.
Finally, let’s consider the case where both uses want to sell complementary assets. Jack places an order to sell 20 YES @$0.75. Jill places an order to sell 20 NO @$0.25. A match is again possible in this scenario, this time employing the merge operation. It works as follows, transfer 20 YES from Jack and 20 NO from Jill and merge them via the CTF into $20 and distribute $15 to Jack and $5 to Jill.
As illustrated with the examples and diagram above, bids for token A at price P are equivalent to asks for token A’ (the complement) at price 1-P and vise versa. Liquidity is thus shared between complementary binary outcome tokens. Polymarket’s audited custom exchange contract (CTFExchange) supports this unified book structure and the matching service calculates matches accordingly.
The goal of Polymarket’s market making reward program is to catalyze a healthy and liquid marketplace. Specifically, this means encouraging passive, balanced quoting that is tight to a market’s midpoint throughout the duration of the market’s lifecycle and done so across many unique markets. In order to do this, we have taken dYdX’s successful and ongoing incentive program, which shares a similar goal, and made a few adjustments to better suit our market structure and platform idiosyncrasies. The primary changes include specific consideration for binary market liquidity (ie a bid on A counts as an ask on A’), the lack of any staking mechanic, a slightly modified order utility-relative to depth function and reward amounts isolated per market. Rewards are distributed using a fork of 1inch’s merkle distributor (itself a fork of Uniswap’s merkle distributor) at the cadence of weekly epochs. Rewards are currently funded in $UMA as provided by the UMA DAO (proposal).
By posting resting limit orders, market makers are automatically eligible for Polymarket’s incentive program. Scoring happens according to the following formulas documented here which factor two-sided quoting with complementary consideration, quote tightness (vs midpoint) and uptime. The amount of rewards earned is determined by the relative share of each participant’s $Q_{epoch}$ in each market multiplied by the rewards available for that market. Sampling of orders happens randomly every minute. We will walk through a complete scoring example below.
Let’s consider two scoring markets with the following configurations.
Market X (Outcome Tokens X and X’):
$size_{min}$ - 100 shares
$spread_{max}$ - 5c
$reward$ - 75
Market Y (Outcome Tokens Y and Y’):
$size_{min}$ - 10 shares
$spread_{max}$ - 3c
$reward$ - 100
Let’s assume there are only two market makers with live limit orders when we sample. Their orders are listed below with the equivalent order for the outcome’s complement in parentheses.
User A:
100 BID X @ .32 (== 100 ASK X’ @.68)
700 BID X @.31 (== 700 ASK X’ @.69)
300 BID X’ @.62 (== 300 ASK X @.38)
1000 BID X’ @.60 (== 1000 ASK X @.40)
500 BID Y @.71 (== 500 ASK Y’ @.29)
200 BID Y @.70 (== 200 ASK Y’ @.30)
420 BID Y @.69 (== 420 ASK Y’ @.31)
100 BID Y’ @.27 (== 100 ASK Y @.73)
User B:
50 BID X @.34 (== 50 ASK X’ @.66)
5 BID X @.33 (== 5 ASK X’ @.67)
100 ASK X @.36 (== 100 BID X’ @.64)
15 BID Y’ @.27 (== 15 ASK Y @.73)
10 ASK Y’ @.29 (== 10 BID Y @.71)
To score orders at time $n$ , we begin by filtering orders that don’t meet the configured $size_{min}$ scoring requirements. We apply the following filter:
Only User B’s order #2 is removed by this filter.
Next, we must calculate the midpoint for the outcome tokens. Based on the orders listed above we determine the midpoint of X to be .35, thus the midpoint of X’ is 1-.35 = .65. Similarly we calculate the midpoint of Y to be .72 based on the orders listed above and the midpoint of Y’ to be 1-.72 = .28. Now that we know our midpoints and based on the configured $spread_{max}$ we can filter orders to only include those within the following range:
Respectively, we calculate the following market ranges (.30, .40), (.60, .70), (.69, .75), (.25, .31). After applying this filter the following orders for User A remain: 1, 2, 3, 5, 6, 8. Then for User B 1, 3, 4, 5 remain.
The next step is to actually score each individual order. We do so by applying the order position scoring function:
We will work through scoring User A’s order #1 in full. For this order, the $spread_{order}$ is 3 (3c from the midpoint), and the $spread_{max}$ is 5 thus it receives a score of (5-3/5)^2 = .16. Applying the same calculation to the rest of the orders we get the following values for User A’s orders respectively .04, .16, .44, .11, .44 and for User B’s remaining orders we get the respective values: .64, .64, .44, .44.
After calculating the individual order scores, we must calculate the “side scores”, weighted by order sizes and considering complements. To do this, we group orders on the same sides considering them within the view of one book. This means for each user there will be two groups per market. For user A the first group of remaining orders for market X is 1 and 2 and the second group is 3. For Market Y user A’s two groups are 5 and 6 then 8. Repeating the same for user B we get the following groupings. User B’s group 1 for market X is 1, group 2 is 3 then for market Y group 1 is 4 and group 2 is 5.
Now, for each group we calculate the “side score”, $Q$ , we apply the function:
So for user A, market X, group 1 we get .16*100 +.04*700=44; for user A market X group 2 we get .16300=48; for user A market Y group 1 we get .44*500 + .11*200=242; and then finally for user A market Y group 2 we get .44*100=44. For user B we get the following four values in the same order as user A: 32, 64, 6.6, 4.4.
Next, we reduce all the “side scores” for each user into a single “market score” per user per market. To do this, we simply take the minimum of the user’s two “side scores” for the market formalized as:
User A has “market scores” 44 for market X and 44 for market Y. User B has “market scores” 32 and 4.4.
These “market scores” are accumulated over an entire epoch and then multiplied by the percentage of samples where the user scored; this yields a user’s $Q_{epoch}$ for a market.
Considering the case above as a one sample epoch, both users have an uptime of 1 for each market. Therefore, the users’ $Q_{epoch}$ are equal to their “market scores”.
Finally, rewards are distributed based on a user’s relative $Q_{epoch}$ for a market and the configured reward, also expressed as:
For user A, from market X they will receive (44/(44+32))*75 = 43.42 reward tokens. For market Y, user A will receive (44/(44+4.4))*100 = 90.90 reward tokens. For market X and Y respectively user B will receive 31.58 and 9.1 reward tokens respectively.
Full documentation on the Polymarket Market Maker reward program can be found alongside the API docs here. Information on the specific reward configuration can be discovered by making a GET request to the following endpoint specifying the epoch number. https://strapi-matic.poly.market/reward-epoches/{epoch_number}
.