Case Study 2: LMSR in Action --- Analyzing Manifold Markets' Market Maker

Background

Manifold Markets is one of the most popular prediction market platforms in the world. Launched in 2022, it allows anyone to create a market on any question, from "Will GPT-5 be released by the end of 2025?" to "Will my neighbor's cat come home this week?"

A key feature that enabled Manifold's rapid growth was its automated market maker. When a user creates a market, the platform's AMM immediately provides liquidity, so the very first trader can buy or sell without waiting for someone to take the other side.

In this case study, we will analyze how an AMM-driven platform like Manifold works in practice: tracing price movements through the cost function, measuring slippage, evaluating liquidity efficiency, and understanding the design trade-offs.

Note

Manifold Markets has evolved its AMM mechanism over time, transitioning between different designs. This case study uses a simplified LMSR-style model to illustrate the core concepts. The actual implementation details may differ.


Phase 1: How the AMM Works on Manifold

Market Creation

When a user creates a binary market on Manifold, the platform:

  1. Initializes the AMM with a default liquidity parameter (historically, Manifold used a system where users could add liquidity subsidies)
  2. Sets the initial probability (either 50% or a creator-specified value)
  3. Opens the market for trading

The Economics of Play Money vs. Real Money

Manifold historically used play money ("mana"), making the AMM subsidy cheaper in real-dollar terms. This is a crucial design choice:

  • Play money AMMs can afford higher $b$ values (more liquidity) because the subsidy does not cost real dollars
  • This encourages experimentation and broad participation
  • Price accuracy depends on traders being motivated even without real financial stakes

Trading Mechanics

When a user trades on Manifold:

  1. They specify a direction (buy Yes or buy No) and an amount of mana to spend
  2. The AMM calculates how many shares they receive for that mana amount
  3. The market state updates, and the new probability is displayed

This is the "spend X mana, receive Y shares" model, which is slightly different from the "buy Y shares, pay X mana" model we discussed in the chapter. Both are equivalent --- they are just different ways of interacting with the same cost function.

Buying a Fixed Mana Amount

Given a trader who wants to spend exactly $M$ mana on outcome $i$, we need to find $\Delta q_i$ such that:

$$C(q_1, \ldots, q_i + \Delta q_i, \ldots, q_n) - C(q_1, \ldots, q_n) = M$$

This requires solving the equation for $\Delta q_i$, which can be done numerically (e.g., with binary search or Newton's method).

import numpy as np

class ManifoldStyleLMSR:
    """An LMSR market that supports 'spend M to buy shares' interface."""

    def __init__(self, b=100, initial_prob=0.5):
        self.b = b
        self.num_outcomes = 2
        # Set initial quantities to achieve desired probability
        # p = e^(q0/b) / (e^(q0/b) + e^(q1/b))
        # For 2 outcomes, set q1=0 and solve for q0
        if initial_prob == 0.5:
            self.quantities = np.zeros(2)
        else:
            logit = np.log(initial_prob / (1 - initial_prob))
            self.quantities = np.array([logit * b, 0.0])

    def cost(self, quantities=None):
        if quantities is None:
            quantities = self.quantities
        q_over_b = quantities / self.b
        max_q = np.max(q_over_b)
        return self.b * (max_q + np.log(np.sum(np.exp(q_over_b - max_q))))

    def prices(self):
        q_over_b = self.quantities / self.b
        max_q = np.max(q_over_b)
        exp_q = np.exp(q_over_b - max_q)
        return exp_q / np.sum(exp_q)

    def probability(self):
        """Return P(Yes)."""
        return self.prices()[0]

    def buy_with_budget(self, outcome, budget):
        """
        Buy shares of `outcome` by spending exactly `budget` mana.
        Returns the number of shares received.
        Uses binary search to find the right quantity.
        """
        # Binary search for quantity that costs exactly `budget`
        lo, hi = 0.0, budget * 10  # Upper bound: generous estimate
        for _ in range(100):  # Sufficient iterations for convergence
            mid = (lo + hi) / 2
            cost_before = self.cost()
            new_q = self.quantities.copy()
            new_q[outcome] += mid
            cost_after = self.cost(new_q)
            trade_cost = cost_after - cost_before
            if trade_cost < budget:
                lo = mid
            else:
                hi = mid
        shares = (lo + hi) / 2

        # Execute the trade
        actual_cost = self.cost(self.quantities.copy())
        self.quantities[outcome] += shares
        actual_cost = self.cost() - actual_cost

        return shares, actual_cost

    def sell_shares(self, outcome, quantity):
        """Sell shares and receive mana."""
        cost_before = self.cost()
        self.quantities[outcome] -= quantity
        cost_after = self.cost()
        mana_received = cost_before - cost_after
        return mana_received

    def execute_trade(self, outcome, quantity):
        """Buy quantity shares; return cost."""
        cost_before = self.cost()
        self.quantities[outcome] += quantity
        return self.cost() - cost_before

Phase 2: Tracing Price Movements Through a Real-World Scenario

Let us model a realistic market lifecycle on a Manifold-style platform.

Market: "Will the city approve the new bus route by March 2026?"

Created on January 15, 2026. Resolution: March 31, 2026.

Parameters: - $b = 200$ (moderate liquidity for a popular local topic) - Initial probability: 50%

market = ManifoldStyleLMSR(b=200, initial_prob=0.5)

# Simulate the market lifecycle
events = [
    # (date, trader, action, outcome, amount_or_qty, context)
    ("Jan 16", "Transit Fan", "buy_yes", 0, 100,
     "City council member tweeted support"),
    ("Jan 18", "Skeptic123", "buy_no", 1, 50,
     "Budget concerns raised in local paper"),
    ("Jan 22", "UrbanPlanner", "buy_yes", 0, 200,
     "Insider: draft proposal looks good"),
    ("Jan 25", "NIMBYnick", "buy_no", 1, 150,
     "Petition against the route gains signatures"),
    ("Feb 1",  "DataDriven", "buy_yes", 0, 80,
     "Historical data: similar proposals pass 70% of time"),
    ("Feb 5",  "CityWatcher", "buy_yes", 0, 300,
     "Council committee recommends approval"),
    ("Feb 10", "NIMBYnick", "buy_no", 1, 100,
     "New opposition from business owners"),
    ("Feb 15", "Transit Fan", "buy_yes", 0, 150,
     "Mayor endorses the route publicly"),
    ("Feb 20", "Hedger", "buy_no", 1, 60,
     "Small hedge; worried about procedural delays"),
    ("Feb 28", "BigBuyer", "buy_yes", 0, 500,
     "Leaked memo: approval scheduled for March meeting"),
]

print(f"Market: Will the city approve the new bus route by March 2026?")
print(f"AMM: LMSR with b={market.b}")
print(f"Initial probability: {market.probability():.1%}")
print()

total_collected = 0
print(f"{'Date':<8} {'Trader':<14} {'Side':<4} {'Mana':>6} {'Shares':>8} "
      f"{'Eff.Price':>9} {'P(Yes)':>7}  Context")
print("-" * 100)

for date, trader, action, outcome, mana, context in events:
    shares, cost = market.buy_with_budget(outcome, mana)
    total_collected += cost
    eff_price = cost / shares if shares > 0 else 0
    p = market.probability()
    side = "YES" if outcome == 0 else "NO"
    print(f"{date:<8} {trader:<14} {side:<4} {mana:>5}M {shares:>7.1f} "
          f"  {eff_price:>7.4f} {p:>7.1%}  {context}")

print()
print(f"Final probability: {market.probability():.1%}")
print(f"Total mana collected: {total_collected:.0f}M")
print(f"Yes shares outstanding: {market.quantities[0]:.1f}")
print(f"No shares outstanding: {market.quantities[1]:.1f}")
print(f"Maximum possible loss: {market.b * np.log(2):.0f}M")

Expected Output (Approximate)

Market: Will the city approve the new bus route by March 2026?
AMM: LMSR with b=200
Initial probability: 50.0%

Date     Trader         Side   Mana   Shares  Eff.Price  P(Yes)  Context
----------------------------------------------------------------------------------------------------
Jan 16   Transit Fan    YES    100M   175.5     0.5698   56.9%  City council member tweeted support
Jan 18   Skeptic123     NO      50M    92.3     0.5417   53.7%  Budget concerns raised in local paper
Jan 22   UrbanPlanner   YES    200M   326.0     0.6135   63.6%  Insider: draft proposal looks good
Jan 25   NIMBYnick      NO     150M   277.1     0.5413   56.9%  Petition against the route gains signatures
Feb 1    DataDriven     YES     80M   133.2     0.6006   60.6%  Historical data: similar proposals pass
Feb 5    CityWatcher    YES    300M   443.2     0.6771   72.3%  Council committee recommends approval
Feb 10   NIMBYnick      NO     100M   180.2     0.5556   67.6%  New opposition from business owners
Feb 15   Transit Fan    YES    150M   210.2     0.7135   74.1%  Mayor endorses the route publicly
Feb 20   Hedger         NO      60M   108.9     0.5510   71.5%  Small hedge; worried about delays
Feb 28   BigBuyer       YES    500M   596.4     0.8387   83.8%  Leaked memo: approval scheduled

Price Path Analysis

The market tells a story through its price movements:

  1. January 16-18 (50% to 54%): Moderate bullish sentiment meets moderate skepticism. The market barely moves from the starting point.

  2. January 22 (54% to 64%): UrbanPlanner's large purchase based on inside knowledge creates a significant move. This is the AMM doing its job --- incorporating insider information into the price.

  3. January 25 (64% to 57%): NIMBYnick pushes back with a large "No" purchase. The market price reflects genuine uncertainty.

  4. February 1-5 (57% to 72%): Two "Yes" purchases in sequence push the price decisively higher. The committee recommendation is a strong signal.

  5. February 10-20 (72% to 72%): Back-and-forth trading keeps the price in a narrow range, reflecting balanced information flow.

  6. February 28 (72% to 84%): BigBuyer's massive purchase on leaked information pushes the price to its highest level.


Phase 3: Measuring Slippage

Slippage matters because it represents the "hidden cost" of trading. Let us measure it for each trade.

def measure_slippage_for_trade(market_b, quantities_before, outcome, budget):
    """
    Measure slippage for a budget-based trade.

    Returns slippage as percentage of budget.
    """
    # Create a temporary market at the given state
    temp = ManifoldStyleLMSR(b=market_b)
    temp.quantities = quantities_before.copy()

    # Ideal: shares at current price
    current_price = temp.prices()[outcome]
    ideal_shares = budget / current_price

    # Actual: shares received
    actual_shares, actual_cost = temp.buy_with_budget(outcome, budget)

    # Slippage
    share_shortfall = ideal_shares - actual_shares
    slippage_pct = (share_shortfall / ideal_shares) * 100

    return {
        'current_price': current_price,
        'ideal_shares': ideal_shares,
        'actual_shares': actual_shares,
        'share_shortfall': share_shortfall,
        'slippage_pct': slippage_pct,
        'effective_price': actual_cost / actual_shares
    }

Slippage Table for Our Market

Trade Budget Starting P(Yes) Ideal Shares Actual Shares Slippage %
Transit Fan (100M Yes) 100M 50.0% 200.0 ~175.5 ~12.3%
Skeptic123 (50M No) 50M 56.9% ~116.0 ~92.3 ~20.4%
UrbanPlanner (200M Yes) 200M 53.7% ~372.4 ~326.0 ~12.5%
BigBuyer (500M Yes) 500M 71.5% ~699.3 ~596.4 ~14.7%

Key observations:

  1. Slippage increases with trade size relative to $b$. The 500M trade has higher absolute slippage than the 50M trade.

  2. Slippage is higher when price is near 50%. Near the midpoint, the cost function's curvature (second derivative) is at its minimum, which actually means the price moves most predictably. However, relative slippage can be higher for trades that push through the 50% region.

  3. Slippage is a feature, not a bug. It prevents any single trader from cheaply dominating the market. BigBuyer paid a premium precisely because they were trying to move the price significantly.


Phase 4: Evaluating Liquidity Efficiency

What Is Liquidity Efficiency?

Liquidity efficiency measures how much useful price movement you get per dollar of subsidy:

$$\text{Efficiency} = \frac{\text{Information incorporated (price accuracy)}}{\text{Subsidy cost}}$$

This is hard to measure directly, but we can examine proxies.

Subsidy Utilization

With $b = 200$, the maximum subsidy is $200 \cdot \ln(2) \approx 138.6$ mana.

From our simulation: - Total mana collected: ~1,690M - If "Yes" wins (at P=84%): Payout = ~1,287 Yes shares at 1M each = 1,287M. Loss = 1,287 - 1,690 = -403M... wait, that would be a profit.

Actually, let us recalculate more carefully:

# After simulation
total_yes_shares = market.quantities[0]
total_no_shares = market.quantities[1]
initial_cost = market.b * np.log(2)
current_cost = market.cost()
money_collected = current_cost - initial_cost

print(f"Money collected from all trades: {money_collected:.0f}M")
print(f"Yes shares outstanding: {total_yes_shares:.0f}")
print(f"No shares outstanding: {total_no_shares:.0f}")
print()

# If Yes wins
payout_yes = max(total_yes_shares, 0)
pnl_yes = money_collected - payout_yes
print(f"If Yes wins: Payout={payout_yes:.0f}M, P&L={pnl_yes:.0f}M")

# If No wins
payout_no = max(total_no_shares, 0)
pnl_no = money_collected - payout_no
print(f"If No wins: Payout={payout_no:.0f}M, P&L={pnl_no:.0f}M")

The Subsidy in Context

The AMM subsidy is the cost of running this information market. In exchange for at most 139M in potential loss, the platform obtained:

  1. A continuously updated probability estimate that tracked real-world events (committee vote, mayor endorsement, leaked memo)
  2. An engagement mechanism that attracted 8 distinct traders over 6 weeks
  3. A permanent record of how the community's beliefs evolved over time

Comparison: What if $b$ Were Different?

$b$ value Max loss Price sensitivity (10 shares) Suitability
50 34.7M ~5.0% move Too volatile for serious questions
100 69.3M ~2.5% move Good for moderately traded markets
200 138.6M ~1.25% move Good for popular markets
500 346.6M ~0.5% move Only for very high-volume markets

For our bus route market with ~1,690M in total trading volume, $b = 200$ seems appropriate. Prices moved meaningfully with each trade but were not so volatile as to be unreliable.


Phase 5: Lessons for Platform Designers

Lesson 1: The AMM Is the Foundation of Platform Viability

Without the AMM, this market could not have functioned. The first trader (Transit Fan) arrived and immediately traded against the AMM. If the platform required a human counterparty, Transit Fan would have posted an order and waited --- possibly forever.

Lesson 2: Slippage Is an Implicit Tax on Large Trades

Platforms should clearly communicate slippage to users. Showing the effective price before trade execution helps users make informed decisions. Some platforms offer a "slippage preview" or "price impact warning" for large trades.

Lesson 3: The Liquidity Parameter Should Scale with Expected Volume

Our market received ~1,690M in total volume. The $b = 200$ parameter meant that total volume was about $8.5b$ --- a healthy ratio. If we had set $b = 50$, the same trading would have pushed prices to extreme values very quickly, possibly discouraging participation.

Lesson 4: AMMs Naturally Incorporate Information

Look at the price trajectory: it tracked real-world events remarkably well. The committee recommendation and mayor endorsement pushed the price up; opposition pushed it down. The final price of ~84% reflected the consensus belief --- and if the route is approved, this was a well-calibrated market.

Lesson 5: Play Money Changes the Calculus

With play money, the AMM subsidy is not a real-dollar expense. This allows platforms like Manifold to set higher $b$ values and provide more liquidity per market, improving user experience. The trade-off is that play-money markets may be less accurate because traders have less "skin in the game."


Phase 6: What Manifold Actually Did (Historical Context)

Early Design (2022)

Manifold initially used a variant of the CPMM mechanism. Each market had a pool of "Yes" and "No" shares, and the $x \cdot y = k$ invariant determined prices.

Evolution to DPM (Dynamic Parimutuel Market)

Manifold experimented with a Dynamic Parimutuel Market mechanism, which was a custom design that shared some features with LMSR and CPMM but had unique properties.

Transition to CPMM (2023)

Manifold moved to a more standard CPMM approach with a mechanism for adding and removing liquidity. Users could add mana to the liquidity pool, increasing $k$ and earning a share of trading activity.

Key Design Decisions

  1. User-funded liquidity: Unlike pure LMSR where the operator funds the subsidy, Manifold allowed market creators and other users to add liquidity. This distributed the subsidy cost.

  2. Variable liquidity per market: Popular markets attracted more liquidity providers, naturally scaling $b$ (or its CPMM equivalent) with demand.

  3. Ante system: Market creators were required to put up a minimum ante (a liquidity subsidy) when creating a market, ensuring every market had baseline liquidity.


Quantitative Exercises

Exercise A: Sensitivity Analysis

Using the bus route market scenario, re-run the simulation with $b = 100$ and $b = 500$. For each: 1. Calculate the final probability 2. Measure total slippage across all trades 3. Compute the AMM P&L under both resolution outcomes 4. Which $b$ produces the most "informative" price path?

Exercise B: Optimal Trade Size

BigBuyer spent 500M in a single trade, experiencing significant slippage. Would they have been better off splitting this into five trades of 100M each? Simulate both scenarios and compare the total shares received.

Important caveat: In a competitive market, splitting trades can be worse because other traders may trade between your splits (this is called "front-running"). But in a thin market, this risk is lower.

Exercise C: Calibration Check

The market ended at ~84% probability. If we had 100 markets that all ended at 84%, we would expect about 84 of them to resolve "Yes." This is the calibration standard.

Design a simulation that: 1. Creates 100 markets, each with a "true" probability drawn uniformly from [0, 1] 2. Simulates traders who trade noisily in the direction of the true probability 3. Records the final market price and the actual resolution 4. Plots a calibration curve (market price vs. resolution frequency) 5. Evaluates how well the LMSR AMM calibrates under different $b$ values


Discussion Questions

  1. Manifold transitioned from one AMM mechanism to another. What factors might drive a platform to change its core AMM mechanism?

  2. The case study used $b = 200$ for a community question. How would you determine $b$ for a much higher-stakes question, like "Will the central bank raise interest rates?"

  3. If you were designing a competitor to Manifold, what AMM features would you prioritize? Would you use LMSR, CPMM, LS-LMSR, or something else?

  4. The market's final probability was 84%. If the bus route is NOT approved, does this mean the market "failed"? How should we evaluate prediction market accuracy for single events?

  5. Could this market design work with real money instead of play money? What changes would be necessary (regulatory, economic, or technical)?