-
Notifications
You must be signed in to change notification settings - Fork 23
Add reward calculation example #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Polymarket Reward Calculator Example | ||
|
|
||
| This example demonstrates how to calculate estimated liquidity rewards for a specific Polymarket event programmatically. | ||
|
|
||
| ## Features | ||
|
|
||
| - Fetches market data using the Gamma API. | ||
| - Fetches live order book data using the CLOB API. | ||
| - **Crucial:** Demonstrates how to correctly sort the order book (Bids Descending, Asks Ascending) to avoid pricing errors. | ||
| - Calculates market depth using an **additive spread** logic (e.g., +/- 1 cent), which is essential for accurately assessing liquidity in low-priced markets. | ||
| - Estimates daily rewards based on a hypothetical investment amount. | ||
|
|
||
| ## Usage | ||
|
|
||
| 1. Ensure you have Node.js installed. | ||
| 2. Run the script: | ||
|
|
||
| ```bash | ||
| npx ts-node calculate-rewards.ts | ||
| ``` | ||
|
|
||
| ## Logic Overview | ||
|
|
||
| The core calculation logic addresses common pitfalls when working with raw order book data: | ||
|
|
||
| 1. **Sorting:** Raw API data may not be sorted. We explicitly sort bids by price (high->low) and asks by price (low->high). | ||
| 2. **Spread:** Instead of a percentage-based spread (which fails on low prices like $0.02), we use a fixed price delta (e.g., +/- $0.01). | ||
| 3. **Depth:** We sum the USD value (`price * size`) of all orders within the spread range to determine the effective liquidity. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| import fetch from 'node-fetch'; // Note: In a real repo, might need to install this or use native fetch in Node 18+ | ||
|
|
||
| // --- Configuration --- | ||
| const EVENT_SLUG = "russia-x-ukraine-ceasefire-in-2025"; // Change this to test other markets | ||
| const INVESTMENT_AMOUNT = 1000; // USD | ||
|
|
||
| // --- Interfaces --- | ||
| interface Order { | ||
| price: string; | ||
| size: string; | ||
| } | ||
|
|
||
| interface OrderBook { | ||
| bids: Order[]; | ||
| asks: Order[]; | ||
| } | ||
|
|
||
| interface Market { | ||
| question: string; | ||
| clobTokenIds: string; // JSON string | ||
| outcomes: string; // JSON string | ||
| clobRewards: { rewardsDailyRate: number }[]; | ||
| } | ||
|
|
||
| // --- Main Logic --- | ||
| async function calculateRewards() { | ||
| console.log(`\n--- Polymarket Reward Calculator Example ---`); | ||
| console.log(`Target Event: ${EVENT_SLUG}`); | ||
| console.log(`Investment: $${INVESTMENT_AMOUNT}\n`); | ||
|
|
||
| try { | ||
| // 1. Fetch Event/Market Data | ||
| const eventUrl = `https://gamma-api.polymarket.com/events?slug=${EVENT_SLUG}`; | ||
| const eventResp = await fetch(eventUrl); | ||
| const eventData = await eventResp.json(); | ||
|
|
||
| if (!eventData || eventData.length === 0) { | ||
| throw new Error("Event not found. Check the slug."); | ||
| } | ||
|
|
||
| const market = eventData[0].markets[0] as Market; | ||
| const dailyRewardPool = market.clobRewards?.[0]?.rewardsDailyRate || 0; | ||
|
|
||
| console.log(`Market Question: ${market.question}`); | ||
| console.log(`Daily Reward Pool: $${dailyRewardPool.toFixed(2)}\n`); | ||
|
|
||
| const tokenIds: string[] = JSON.parse(market.clobTokenIds); | ||
| const outcomes: string[] = JSON.parse(market.outcomes); | ||
|
|
||
| // 2. Process each outcome | ||
| for (let i = 0; i < tokenIds.length; i++) { | ||
| const tokenId = tokenIds[i]; | ||
| const outcome = outcomes[i]; | ||
|
|
||
| // 3. Fetch Order Book | ||
| const bookUrl = `https://clob.polymarket.com/book?token_id=${tokenId}`; | ||
| const bookResp = await fetch(bookUrl); | ||
| const orderBook = (await bookResp.json()) as OrderBook; | ||
|
|
||
| // 4. Sort Order Book (CRITICAL STEP) | ||
| // Bids: Descending (Highest price first) | ||
| // Asks: Ascending (Lowest price first) | ||
| const bids = (orderBook.bids || []).map(b => ({ price: parseFloat(b.price), size: parseFloat(b.size) })) | ||
| .sort((a, b) => b.price - a.price); | ||
| const asks = (orderBook.asks || []).map(a => ({ price: parseFloat(a.price), size: parseFloat(a.size) })) | ||
| .sort((a, b) => a.price - b.price); | ||
|
|
||
| // Determine Mid Price | ||
| const bestBid = bids.length > 0 ? bids[0].price : undefined; | ||
| const bestAsk = asks.length > 0 ? asks[0].price : undefined; | ||
|
|
||
| if (bestBid === undefined || bestAsk === undefined) { | ||
| console.log(`Outcome: ${outcome} - Skipping (One-sided or empty order book)`); | ||
| continue; | ||
| } | ||
|
|
||
| const midPrice = (bestBid + bestAsk) / 2; | ||
|
|
||
| console.log(`Outcome: ${outcome} (Mid Price: ${midPrice.toFixed(4)})`); | ||
|
|
||
| // 5. Calculate Depth and Rewards for different spreads | ||
| const spreads = [ | ||
| { label: "1 cent", val: 0.01 }, | ||
| { label: "2 cents", val: 0.02 }, | ||
| { label: "3 cents", val: 0.03 } | ||
| ]; | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Calculate total probability (sum of mid prices) for normalization | ||
| // Note: In a real scenario, we should sum mid prices of ALL outcomes first. | ||
| // For this simple script, we'll assume the user runs it for all outcomes and manually checks. | ||
| // But to be more accurate per outcome, we can just use the midPrice as a proxy for probability. | ||
| // A better approach for the script is to fetch ALL outcomes first, sum their midPrices, then loop. | ||
|
|
||
| for (const spread of spreads) { | ||
| // Use Additive Spread (e.g. +/- 1 cent) for accurate low-price handling | ||
| const minBid = Math.max(0, midPrice - spread.val); // Clamp to 0 to avoid negative price filter | ||
| const maxAsk = midPrice + spread.val; | ||
|
|
||
| const validBids = bids.filter(b => b.price >= minBid); | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const validAsks = asks.filter(a => a.price <= maxAsk); | ||
|
|
||
| // Calculate Depth in USD (Price * Size) | ||
| const bidDepth = validBids.reduce((sum, b) => sum + (b.price * b.size), 0); | ||
| const askDepth = validAsks.reduce((sum, a) => sum + (a.price * a.size), 0); | ||
| const totalDepth = bidDepth + askDepth; | ||
|
|
||
| // Estimate Reward | ||
| // Formula: DailyPool * (UserInv / (TotalDepth + UserInv)) | ||
| // We allocate the daily pool based on the outcome's probability (approx. midPrice) | ||
| // To be strictly correct, we should normalize if sum(midPrices) != 1. | ||
| // For this example, we'll use midPrice directly as the probability estimate. | ||
| const outcomeRewardPool = dailyRewardPool * midPrice; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Reward pool allocation can exceed 100%The calculation multiplies |
||
| const estReward = outcomeRewardPool * (INVESTMENT_AMOUNT / (totalDepth + INVESTMENT_AMOUNT)); | ||
|
|
||
| console.log(` Spread +/- ${spread.label} ($${minBid.toFixed(3)} - $${maxAsk.toFixed(3)}):`); | ||
| console.log(` Depth: $${totalDepth.toFixed(2)} (Bids: $${bidDepth.toFixed(0)}, Asks: $${askDepth.toFixed(0)})`); | ||
| console.log(` Est. Daily Reward: $${estReward.toFixed(2)}`); | ||
| } | ||
| console.log(""); | ||
| } | ||
|
|
||
| } catch (error) { | ||
| console.error("Error:", error); | ||
| } | ||
| } | ||
|
|
||
| calculateRewards(); | ||
Uh oh!
There was an error while loading. Please reload this page.