# ZIP-6: Parimutuel <!-- prettier-ignore --> :::info Author: Dr. Malte Kliemann Revision: 4 ::: ## Overview Parimutuels are "losers pay winners" market makers. Any informant can bet any amount at any time. Their bet amount goes into the _pot_ and they receive tokens which represent their bet. After the market has resolved, the entire pot is distributed amongst those who bet on the outcome that materialized, proportional to what their share of the pot is. Let's imagine a simple prediction market based on a horse race. There are five horses running in this race: A, B, C, D and E. People are placing bets on which horse they believe will win. Suppose that at this point, the total amount of money wagered on these horses is as follows: - A: $200 - B: $300 - C: $100 - D: $250 - E: $150 Altogether, the total pool of money that's been wagered is $1,000. If you bet on Horse A, and Horse A wins, for each dollar you bet, you'd get the total bets on all horses ($1,000) divided by the amount bet on Horse A ($200). So for every dollar you bet, you would get $5 back - this includes the return of your original dollar plus $4 in winnings. The market predicts the probability of A winning the race as 20%. Similarly, if you bet on Horse B, and Horse B wins, for each dollar you bet, you'd get the total bets on all horses ($1,000) divided by the amount bet on Horse B ($300). So for every dollar you bet, you would get roughly $3.33 back - this includes the return of your original dollar plus about $2.33 in winnings. The market predicts the probability of B winning the race as 30%. And so on for the rest of the horses... Somewhat similar to LS-LMSR, this means that the market becomes more and more liquid as more informants trade. Parimutuel systems can also accommodate scalar markets, where outcomes fall within a specified range rather than being a simple binary Yes/No. For example, let's consider a scalar market with a range of $[5, 15]$. Suppose the actual result turns out to be $12.5$. In this case, the longs—who bet on the result being higher—would receive 75% of the total pot. This is because the result of 12.5 falls 75% of the way along the range from 5 to 15. Conversely, the shorts—who bet on the result being lower—would receive the remaining 25% of the pot, since the result is 25% below the maximum of the range. Parimutuel has several disadvantages: - The odds are not fixed when you buy tokens. For example, if you buy from LMSR at a price of 0.33, then you know that you'll get a 300% payoff if you're right (ignoring slippage). That's not the case at a parimutuel. If more people buy your outcome, your payoff gets worse. This makes it impossible to properly reward traders that have moved the price in the right direction and have done so early. A particularly vexing symptom of this problem is that, if a market becomes trivialized (some outcome $X$ has materialized before the end of the market) and at least two agents have bet on the winning outcome, then it's a winning strategy to keep pumping more money into the market to dilute the other agent's stake. The only fix is to close the market early. However, closing markets early has proven to be a flimsy process on a decentralized platform. It also raises other problems. If Alice just bet a large amount to dilute Bob's stake and Bob is about to bet to counteract this dilution, is it really fair to close the market now? - No selling of contracts. Once you've bought a contract, you have to hold it. You can't just take back your bet. AMMs allow selling because you're selling at a worse price. Ambiguous resolutions, however, work very well with parimutuels: Everybody just gets their money back. This virtually impossible to do for AMMs where selling is allowed, but it's very simple here for two reasons: 1) No money ever leaves the pot before resolution. 2) There's no need to issue extra Ambiguous tokens to keep track of how much was invested in the pot - this is already tracked by the parimutuel market maker. DPM (dynamic parimutuel markets) developed in [P04] allows selling bets. ## Definitions ### Pot An account which holds all the wagered funds. ### Parimutuel Shares Parimutuel shares represent a user's bet size and the outcome they bet on. If Alice bets `x` units of collateral on `i`, she receives `x` units of parimutuel shares for `i`. ### Payoff in Categorical Markets The _payoff_ of Alice is the amount of collateral she receives from the pot when the bet is settled. This includes receiving back her end of the bet. For a categorical market, this is straightforward. Let $a = (a_1, \ldots, a_n)$ denote the amounts wagered on the outcomes of the market (the _pot_). The _payoff ratio_ of outcome $i$ is defined as $$ r_i(a) = \frac{s(a)}{a_i}, $$ where $s(a) = \sum_k a_k$. Note that $s(a)$ is equal to the amount of collateral in the pot. Therefore, $r_i(a)$ can be calculated without knowing the total issuances of the parimutuel shares. If $i$ materializes, for each unit of a parimutuel share of outcome $i$, you will receive $r_i(a_1, \ldots, a_n)$ dollars back from the pool. Your profit is $r_i(a_1, \ldots, a_n) - 1$ per unit of parimutuel share bought. ### Payoff in Scalar Markets Calculating payoff for scalar markets requires a bit more work. Let $[x, y]$ be the scalar range and $v$ the resolution. Let $a_{\mathrm{short}}$ and $a_{\mathrm{long}}$ denote the number of SHORT and LONG shares _at resolution_, resp. Define $$ v' = \begin{cases} v & v \in [x, y] \\ x & v < x \\ y & v > y \end{cases} $$ (i.e. $v$ clamped into $[x, y]$). The _correctness_ of short and long is defined as \begin{align*} c_{\mathrm{short}} &= \frac{y - v'}{y - x}, \\ c_{\mathrm{long}} &= \frac{v' - x}{y - x}, \end{align*} resp. Note that $c_s + c_\ell = 1$. The _payoff ratio_ of short/long is then defined as $$ r_\ast = \frac{s(a)}{a_\ast} \cdot c_\ast $$ where $\ast = \mathrm{short}, \mathrm{long}$. Redeeming $x$ units of $\ast = \mathrm{short}, \mathrm{long}$ yields $xr_\ast(a)$ units of collateral. It's very important to note that the total issuances $a_\ast$ must be recorded at resolution. ## Rules of Betting and Claiming Rewards ### Betting There's no real point of adding slippage limits to buying as we do for the AMM. This is because there's always a risk that your share of the pool can is diluted by someone betting after you. Creator fees are paid when users buy parimutuel shares in the usual fashion: If Alice pays `x` units of collateral for parimutuel shares and the creator fees are $f$, then `fx` units of collateral go to the creator and Alice receives only `(1-f)x` units of parimutuel shares. There is, however, a minimum bet size specified as config parameter. The pallet assumes that the minimum bet size is at least the existential deposit of the parimutuel shares, and that the parimutuel shares' ED is greater or equal to the ED of the base asset. This is very useful when redeeming (see below). ### Claiming Rewards If Alice holds $x$ units of parimutuel share $i$. If the market resolves to $k \neq i$, then her shares are completely worthless. If the market resolves to $i$, then Alice receives $xr_i(a)$ from the pot, where $a$ is the current state of the parimutuel. If the unlikely event occurs that the winning token has a total issuance of zero but the pot is not empty, each better can redeem _any_ parimutuel share for exactly one unit of collateral. While it would be more defensive to slash these funds to make exploits harder, this avoid confusion on markets with very low participation ("I bet $100 on A, no one else was interested, B won and now my money is gone?! Why?"). Note that for each $i$, the total amount of collateral in the pot is always greater or equal to the total issuance of the parimutuel shares for $i$. In particular, if we use the same existential deposit for the collateral and the parimutuel shares (or something lower), then there is never any risk of inability to pay winners due to dusting. There's no need to add some "buffer" deposit or whitelist the pot from dusting. ## Parimutuel Arithmetic ### Expected Payoff If you believe an outcome has a probability p of occurring, then the fair return on a winning bet should be $1/p$. This is because, over many repetitions, you'd expect to win once every $1/p$ times. For example, if you believe that $p = 0.25$ , then fair odds would be 4:1. This means for every dollar you bet, you'd expect a return of $4 on a win. In the parimutuel system, the return for each dollar bet on outcome $i$ is $r_i(a)$ = s(a)/a_i$. For this return to be considered "fair" based on your belief about the outcome's probability, it should match the inverse of your believed probability. In other words, if you think there's a 25% chance of an outcome, you'd expect the system to give you 4:1 odds (or a return of $4 for every $1 bet) for it to be a fair bet. If the system offers odds that are better than your believed probability, then you'd consider the bet to have positive expected value (you expect to make a profit in the long run). If the odds are worse, then the bet has negative expected value (you expect to lose money in the long run). In essence, for a bet to be "fair", the expected value should be zero: you neither expect to make nor lose money in the long run. This happens when the system's offered odds match your personal beliefs about the probability of the outcome. Long story short, given a pot balance $a$, the return $r_i(a)$ of a fair bet on $i$ would match the inverse of the probability $p_i(a)$ of $i$. Thus, the prediction/spot price of $i$ is $p_i(a) = r_i(a)^{-1}$. The value $v(a)$ predicted by a scalar market with range $[c, d]$ using parimutuel with state $a$ can then be calculated using the formula $$ v(a) = p_{\mathrm{short}}(a) \cdot c + p_{\mathrm{long}}(a) \cdot d. $$ Calculating the payoff in a scalar market is slightly more involved. ## Architecture The PR will add a new pallet, zrml-parimutuel, which implements the following extrinsics: ```rust /// Buy parimutuel shares. /// /// # Arguments /// /// - `outcome`: The outcome to buy the shares of. /// - `amount`: The amount of collateral to spend and of parimutuel shares to receive. fn buy(outcome: Outcome, amount: BalanceOf<T>) -> DispatchResult; /// Claim winnings from a resolved market. fn claim_rewards(market_id: MarketIdOf<T>) -> DispatchResult; ``` The behavior of `buy` is the following: - Fails if the market belonging to `outcome` doesn't exist. - Fails if `outcome` is not a valid outcome (e.g. the market exists, but doesn't contain that outcome) - Fails if `amount` is below the minimum bet size. - Fails if the market is closed. - Fails if the market does not have the correct `scoring_rule` field. - Subtracts external fees to market creator from `amount`. - Transfers `amount` to the pot. - Transfers `amount` parimutuel shares to the user. The behavior of `claim_rewards` is the following: - Fails if there is no market for `market_id`. - Fails if the market is not resolved. - Fails if the signer doesn't own any winning shares (in particular, this will always fail if the market has the wrong scoring rule). - Fails if the market does not have the correct `scoring_rule` field (defensive check). - Read the market's payoff. If there's no payoff, calculate the payoff and write it. - Transfers $\lfloor x \cdot r_i \rfloor$ units of collateral to the user. Here $x$ is `amount` and $r_i$ is the payoff. The amount is rounded down to avoid exploits. Transfers need to happen with `AllowDeath` to ensure that small residue doesn't prevent the last of the winners to withdraw their rewards. Note that the state of the parimutuel pot is tied to the state of the market, similar to the pools in zrml-neo-swaps. Note that it's not necessary to have a `market_id` parameter, as it is contained in the `outcome` object. The pallet has the following config parameters (in addition to the standard stuff; the implementor may add more if necessary): ```rust type ExternalFees: DistributeFees< Asset = AssetOf<Self>, AccountId = AccountIdOf<Self>, Balance = BalanceOf<Self>, MarketId = MarketIdOf<Self>, >; type MarketCommons: MarketCommonsPalletApi< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, >; type MultiCurrency: MultiCurrency<Self::AccountId, CurrencyId = AssetOf<Self>>; /// The minimum amount each bet must be. Must be larger than the existential deposit of parimutuel shares. #[pallet::constant] type MinBetSize: Get<BalanceOf<Self>>; ``` To facilitate resolution, we define a single storage element `Payoff` which maps market IDs to their payoff in the form of a `BTreeMap` which maps assets to their payoff ratio. We could use a vector, but to have a "pre-sorted" map. ### Refactoring - Move `zrml_neo_swaps::traits::DistributeFee` to `primitives` so it can be used for parimutuel and neo-swaps. ### New Scoring Rule Add `Parimutuel` to the `ScoringRule` enum. All other trading mechanisms must reject trades on a market with this scoring rule. ### Refactoring Assets Refactor `Asset` into two structs in a similar fashion: ```rust pub enum Outcome<MarketId: MaxEncodedLen> { CategoricalOutcome(MarketId, CategoryIndex), ScalarOutcome(MarketId, ScalarPosition), // CombinatorialOutcome, } pub enum Asset<MarketId: MaxEncodedLen> { Outcome(Outcome<MarketId>), PoolShare(SerdeWrapper<PoolId>), #[default] Ztg, ForeignAsset(u32), ParimutuelShare(Outcome<MarketId>), } ``` We're also removing `CombinatorialOutcome` (seems pointless to have a single object for that). Note that this requires a storage migration and some changes on the UI. The advantage of this over just using `Asset::CategoricalOutcome` and `Asset::ScalarOutcome` as parimutuel shares (which would avoid the migration) is defensiveness: There is no risk of accidentally mixing different types of resolution mechanisms, because these mechanisms all have their own asset types. ### Resolution Mechanisms A fine-grained approach to handling how markets resolve and how or if outcome tokens can be created. The introduce the enum `ResolutionMechanism`, which currently contains two variants, `Noop` and `RedeemTokens`: - A market with resolution mechanism `Noop` does not allow complete set operations. Resolving the market has the same effect as `RedeemTokens`: Changing the status to `MarketStatus::Resolved`, writing the `resolved_outcome` field of the market, unreserving/slashing/repatriating bonds and resolving disputes. Calling `redeem_shares` is not allowed. (Throwing an error in `redeem_shares` is purely defensive programming, since there are no outcome tokens in parimutuel markets to begin with.) - A market with resolution mechanism `RedeemTokens` behaves like markets did before. But instead of writing the resolution mechanism into the market struct (which is currently unnecessary, and might always be unnecessary), we implement a function `Market::resolution_mechanism()` which returns the market's resolution mechanism according to the following rules: - If `scoring_rule` is `Parimutuel`, the resolution mechanism is `Noop`. - Otherwise, the resolution mechanism is `RedeemTokens`. When smart contract resolutions are implemented, it may become necessary to write additional info to the market struct, but we should not get ahead of ourselves. Long-term, a separation of concerns between how tokens are minted and redeem must also be implemented. A trading mechanism like Rikiddo (or any other cost function implementation of a scoring rule) is a great example of a market maker which allows redeeming tokens as Arrow securities but does not allow complete set operations. ### Additional Notes - The implementation will use FRAME benchmarks v2. - Parimutuel as implemented here is not suitable for long-running markets. Even on shorter markets, users should be warned that they cannot sell assets. ## Future Work - Dynamic minimum bet size: If Alice bets $1000 on A, then Bob shouldn't be able to bet $1 on B; instead, Bob should have to bet at least 10% of Alice's stake. - Dynamic parimutuel according to [P04]. ## Proofs <!-- prettier-ignore --> :::info Theorem. Consider a scalar market using parimutuel. Let $p_i$ be the pot size after $i$ redeems. In particular, $p_0 = s(a)$. Denote the payoff ratios by $r_\ast$ (as defined above) and by $x_i$ the size of the $i^{\mathrm{th}}$ redeem. Define $a_{\mathrm{short}}^i$ and $a_{\mathrm{long}}^i$ to be the total issuance of short tokens after $i$ redeems, resp. Then $p_i = a_{\mathrm{short}}^i \cdot r_{\mathrm{short}} + a_{\mathrm{long}}^i \cdot r_{\mathrm{long}}$ for all $i$. ::: _Proof._ The proof is done via induction. For $i = 0$, we have $$ a^0_{\mathrm{short}} \cdot r_{\mathrm{short}} + a_{\mathrm{long}}^0 = s(a) c_{\mathrm{short}} + s(a) c_{\mathrm{long}} = s(a) = p^0. $$ Let $i_0 > 0$ and assume the result is true for all $i < i_0$. We assume that the $i^{\mathrm{th}}$ redeem is for SHORT tokens. The case of LONG tokens is symmetrical. \begin{align*} p_i &= p_{i-1} - x_i r_{\mathrm{short}}, \\ a_{\mathrm{short}}^i &= a_{\mathrm{short}}^{i-1} - x_i, \\ a_{\mathrm{long}}^i &= a_{\mathrm{long}}^{i-1}, \end{align*} and thus \begin{align*} a_{\mathrm{short}}^i \cdot r_{\mathrm{short}} + a_{\mathrm{long}}^i \cdot r_{\mathrm{long}} &= a_{\mathrm{short}}^{i-1} \cdot r_{\mathrm{short}} + a_{\mathrm{long}}^{i-1} \cdot r_{\mathrm{long}} - x_ir_{\mathrm{short}} \\ &= p_{i-1} - x_ir_{\mathrm{short}} \\ &= p_i. \end{align*} ## Bibliography - [P04] D. M. Pennock, "A dynamic pari-mutuel market for hedging, wagering, and information aggregation," in Proceedings of the 5th ACM Conference, 2004. DOI: 10.1145/988772.988799 <!-- Links --> [ACV13]: https://web.eecs.umich.edu/~jabernet/papers/ACV12.pdf [CV10]: https://arxiv.org/pdf/1003.0034.pdf [DP21]: https://blog.zeitgeist.pm/introducing-zeitgeists-rikiddo-scoring-rule/ [Conditional Tokens Docs]: https://docs.gnosis.io/conditionaltokens/docs/introduction3/ [MN19]: https://balancer.fi/whitepaper.pdf [OPRS13]: https://www.cs.cmu.edu/~sandholm/liquidity-sensitive%20automated%20market%20maker.teac.pdf [H03a]: https://mason.gmu.edu/~rhanson/mktscore.pdf [H03b]: https://mason.gmu.edu/~rhanson/combobet.pdf [H13]: https://mason.gmu.edu/~rhanson/futarchy2013.pdf [gnosis/conditional-tokens-market-makers]: https://github.com/gnosis/conditional-tokens-market-makers [P04]: https://www.researchgate.net/publication/279714212_A_dynamic_pari-mutuel_market_for_hedging_wagering_and_information_aggregation