prediction_market/
lib.rs

1//! Prediction market library
2//!
3//! Implements the Maniswap algorithm for binary markets. See [BinaryMarket] for details.
4//!
5//! For Maniswap description, see <https://manifoldmarkets.notion.site/Maniswap-ce406e1e897d417cbd491071ea8a0c39>.
6mod types;
7
8pub use types::{Bet, BinaryMarket, Outcome, YesNoValues};
9
10impl BinaryMarket {
11    /// Returns new pool values, and the bet
12    pub fn evaluate_shares(&self, outcome: Outcome, money: u64) -> (YesNoValues<u64>, Bet) {
13        // The AMM receives the order, and converts the $10 into 10 YES shares and 10 NO shares. (Since 1 YES share + 1 NO share always equals $1, the AMM can always issue shares in equal amounts for cash they receive.)
14        let current_product = self.pool.yes * self.pool.no;
15        // println!("current_product: {}", current_product);
16        let mut new_pool = self.pool.map(|pool| pool + money);
17        // println!("new_pool: {:?}", new_pool);
18
19        // The AMM uses a formula based on the number of shares in the liquidity pool to figure out how many YES shares to give back to the trader in return for his wager:
20        // Uniswap-style [constant-product](https://medium.com/bollinger-investment-group/constant-function-market-makers-defis-zero-to-one-innovation-968f77022159#5bc7) formula.
21
22        let div_by = new_pool[-outcome];
23        // println!("div_by: {}", div_by);
24
25        println!(
26            "solving: ({}-x)*{} = {}",
27            new_pool[outcome], new_pool[-outcome], current_product
28        );
29
30        let expected_shares = (current_product as f64 / div_by as f64).ceil() as u64;
31        // println!("expected_shares: {}", expected_shares);
32
33        assert!(expected_shares <= new_pool[outcome]);
34
35        let share_diff = new_pool[outcome] - expected_shares;
36        // println!("share_diff: {}", share_diff);
37
38        new_pool[outcome] = expected_shares;
39
40        (
41            new_pool,
42            Bet {
43                outcome,
44                shares: share_diff,
45            },
46        )
47    }
48
49    pub fn buy_shares(&mut self, outcome: Outcome, money: u64) -> Bet {
50        let (new_pool, bet) = self.evaluate_shares(outcome, money);
51        self.pool = new_pool;
52        bet
53    }
54
55    pub fn probability_of(&self, outcome: Outcome) -> f64 {
56        let total = self.pool.yes + self.pool.no;
57        self.pool[-outcome] as f64 / total as f64
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use proptest::proptest;
65
66    #[test]
67    fn example_from_docs() {
68        // For example, if the AMM initializes the pool with 3 YES shares, and 2 NO shares, the initial constant will be 6. If someone wants to buy $1 of YES, the AMM will update the pool to 4 YES, 3 NO. Since the product of 4*3 is not 6, the AMM will figure out how many YES shares to remove to restore the condition, (4-x)(3) = 6. In this case, x=2, which means the trader will get 2 YES shares back for their $1, and the AMM’s resulting liquidity pool will be 2 YES, 3 NO.
69
70        let market = BinaryMarket {
71            pool: YesNoValues::new(3, 2),
72        };
73
74        let bet = market.evaluate_shares(Outcome::Yes, 1).1;
75
76        assert_eq!(bet.outcome, Outcome::Yes);
77        assert_eq!(bet.shares, 2);
78
79        assert_eq!(market.probability_of(Outcome::Yes), 0.4);
80    }
81
82    #[test]
83    fn bigger() {
84        let market = BinaryMarket {
85            pool: YesNoValues::new(300, 200),
86        };
87
88        let bet = market.evaluate_shares(Outcome::Yes, 200).1;
89
90        assert_eq!(bet.outcome, Outcome::Yes);
91        assert_eq!(bet.shares, 350);
92    }
93
94    #[test]
95    fn rounding_down() {
96        // Round down as it's conservative, to avoid conjuring free shares
97        let market = BinaryMarket {
98            pool: YesNoValues::new(200, 200),
99        };
100
101        let bet = market.evaluate_shares(Outcome::No, 100).1;
102
103        assert_eq!(bet.outcome, Outcome::No);
104        assert_eq!(bet.shares, 166);
105    }
106
107    proptest! {
108        #[test]
109        fn no_money_created(bets: Vec<(Outcome, u8)>){
110            println!("new!");
111            let mut market =  BinaryMarket {
112                pool: YesNoValues::new(100, 100),
113            };
114
115            let mut total_spent = 100;
116            let mut total_shares = YesNoValues::new(0, 0);
117
118            for (outcome, money) in bets {
119                println!("{:?} {}", outcome, money);
120                let money = (money as u64) + 1;  // make it non-zero
121
122                total_spent += money;
123                let bet = market.buy_shares(outcome, money);
124                total_shares[bet.outcome] += bet.shares;
125            }
126
127            println!("total_spent: {}", total_spent);
128            println!("total_shares: {:?}", total_shares);
129
130            assert!(total_spent >= total_shares.yes);
131            assert!(total_spent >= total_shares.no);
132        }
133    }
134}