GooseMarket.

A Waterloo-based prediction market. Students create binary polls and trade YES / NO shares with virtual currency, priced live by an automated market maker. Built as a full-stack team project, from AMM math to admin tooling.

Market

Binary YES / NO polls

Pricing

LS-LMSR market maker

Backend

Flask API + Supabase

Frontend

React + React Query

A campus prediction market

GooseMarket lets students bet on campus questions - who wins an event, whether something ships on time - with virtual currency instead of cash. Each question is a binary market, and the current price reads directly as the crowd's implied probability that it resolves YES.

The interesting part is the pricing. Rather than matching buyers to sellers, an automated market maker always quotes a price using a liquidity-sensitive LMSR, so there is always a trade available and the price moves smoothly as opinion shifts. Around that engine sits a full product: auth, an admin review and resolution flow, tags, positions, streaks, and a leaderboard.

React → Flask → Supabase

Routes & UI

React Router

React Query

fetch + cache

fetch /api/* · cookie session

Auth · Polls · Trades · Admin

REST routes

LS-LMSR engine

live pricing

Supabase client · service-role key

profiles · polls · trades

core tables

tags · poll_tags

topics

The browser never speaks to Supabase directly. Flask proxies every operation with the service-role key and enforces auth via the session cookie.

How a price moves

01

Shares, not odds

Every poll is a binary market. Buying YES or NO mints shares; the market tracks a net quantity per outcome, q_yes and q_no.

02

LMSR cost

Market state is a cost function C(q) = b · log(e^(q_yes/b) + e^(q_no/b)). The price you pay for a trade is exactly the change in C that it causes.

03

Live prices

YES and NO prices are the softmax of the quantities - e^(q/b) normalized to sum to 100¢ - so a price always reads directly as an implied probability.

04

Liquidity that scales

The liquidity parameter isn’t fixed: b = b0 · √Q grows with market size, so early trades move the price meaningfully while thick markets stay stable.

Create → Approve → Trade → Resolve

Create

Any user proposes a poll - title, description, end date, and tags. It enters an admin review queue rather than going live immediately.

Approve

An admin reviews the proposal and approves, edits, or rejects it. Only approved polls become public and tradeable.

Trade

Users buy and sell YES / NO shares. The AMM quotes a live price, and balances and positions update on every trade.

Resolve

After the end date an admin resolves the outcome. Winning shares settle against the balance and the leaderboard updates.

API surface

POST /api/auth/*

login · register · verify · logout, over a cookie session.

GET · POST /api/polls

List all polls or create a new one (pending review).

GET · PUT /api/polls/{id}

Fetch or edit a single poll.

GET /api/polls/{id}/price

Live LS-LMSR YES / NO price, plus /estimate and /stats.

POST /api/trades/buy · sell

Execute a trade against the market maker.

POST /api/positions · /api/user

A user’s open positions and profile / balance.

/api/admin/*

review · approve · reject · update · resolve flows.

GET /api/leaderboard

Ranked traders by realized performance.

Design decisions

Server holds the keys

The Supabase service-role key lives only in the Flask API. The browser talks to /api/* with a cookie session and never touches Supabase directly, so trusted operations stay server-side.

Play money, real incentives

Balances, daily bonuses, and streaks drive engagement without any real cash, and a leaderboard ranks traders - the game-theory of a market without the regulatory weight.

A tested engine

A pytest suite covers the parts that must not break: pricing, buy/sell trades, poll creation, positions, and tags - the AMM math has its own tests.

Built like a product

The repo carries a full engineering doc set - charter, domain model, requirements, use cases, and a test plan and report - alongside the code.

Tech stack

React 19Vite 7React QueryReact RouterTailwind CSSFlaskPython 3.11SupabasePostgrespytestVercel