From c2f301de7de5c8d4c5bd3d96d44ac3f6e01b5189 Mon Sep 17 00:00:00 2001 From: erdinc333 Date: Sun, 23 Nov 2025 19:39:15 +0300 Subject: [PATCH 1/3] Add reward calculation example --- reward-calculation/README.md | 28 ++++++ reward-calculation/calculate-rewards.ts | 117 ++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 reward-calculation/README.md create mode 100644 reward-calculation/calculate-rewards.ts diff --git a/reward-calculation/README.md b/reward-calculation/README.md new file mode 100644 index 0000000..7bbaafd --- /dev/null +++ b/reward-calculation/README.md @@ -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. diff --git a/reward-calculation/calculate-rewards.ts b/reward-calculation/calculate-rewards.ts new file mode 100644 index 0000000..c454413 --- /dev/null +++ b/reward-calculation/calculate-rewards.ts @@ -0,0 +1,117 @@ +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 : 0; + const bestAsk = asks.length > 0 ? asks[0].price : 0; + 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%", val: 0.01 }, + { label: "2%", val: 0.02 }, + { label: "3%", val: 0.03 } + ]; + + for (const spread of spreads) { + // Use Additive Spread (e.g. +/- 1 cent) for accurate low-price handling + const minBid = midPrice - spread.val; + const maxAsk = midPrice + spread.val; + + const validBids = bids.filter(b => b.price >= minBid); + 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)) + // Note: This assumes the pool is split equally or based on probability. + // For simplicity here, we apply the user's share to the *entire* pool for this outcome's liquidity. + // In reality, rewards are split by outcome probability. + const userShare = INVESTMENT_AMOUNT / (totalDepth + INVESTMENT_AMOUNT); + // Adjust reward by probability (approximate using price) + const outcomeRewardPool = dailyRewardPool * midPrice; + const estReward = outcomeRewardPool * userShare; + + 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(); From 61c7cc70ee9f28af7755e85385903bdf1b41a724 Mon Sep 17 00:00:00 2001 From: erdinc333 Date: Sun, 23 Nov 2025 19:51:49 +0300 Subject: [PATCH 2/3] fix: Clarify spread labels and refine reward allocation logic --- reward-calculation/calculate-rewards.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/reward-calculation/calculate-rewards.ts b/reward-calculation/calculate-rewards.ts index c454413..5886281 100644 --- a/reward-calculation/calculate-rewards.ts +++ b/reward-calculation/calculate-rewards.ts @@ -74,11 +74,17 @@ async function calculateRewards() { // 5. Calculate Depth and Rewards for different spreads const spreads = [ - { label: "1%", val: 0.01 }, - { label: "2%", val: 0.02 }, - { label: "3%", val: 0.03 } + { label: "1 cent", val: 0.01 }, + { label: "2 cents", val: 0.02 }, + { label: "3 cents", val: 0.03 } ]; + // 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 = midPrice - spread.val; @@ -94,13 +100,11 @@ async function calculateRewards() { // Estimate Reward // Formula: DailyPool * (UserInv / (TotalDepth + UserInv)) - // Note: This assumes the pool is split equally or based on probability. - // For simplicity here, we apply the user's share to the *entire* pool for this outcome's liquidity. - // In reality, rewards are split by outcome probability. - const userShare = INVESTMENT_AMOUNT / (totalDepth + INVESTMENT_AMOUNT); - // Adjust reward by probability (approximate using price) + // 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; - const estReward = outcomeRewardPool * userShare; + 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)})`); From 6ed77313868b03bb260af20621c755b91864e0f8 Mon Sep 17 00:00:00 2001 From: erdinc333 Date: Sun, 23 Nov 2025 19:56:14 +0300 Subject: [PATCH 3/3] fix: Handle one-sided order books and clamp minBid to 0 --- reward-calculation/calculate-rewards.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/reward-calculation/calculate-rewards.ts b/reward-calculation/calculate-rewards.ts index 5886281..bbdb58a 100644 --- a/reward-calculation/calculate-rewards.ts +++ b/reward-calculation/calculate-rewards.ts @@ -66,8 +66,14 @@ async function calculateRewards() { .sort((a, b) => a.price - b.price); // Determine Mid Price - const bestBid = bids.length > 0 ? bids[0].price : 0; - const bestAsk = asks.length > 0 ? asks[0].price : 0; + 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)})`); @@ -87,7 +93,7 @@ async function calculateRewards() { for (const spread of spreads) { // Use Additive Spread (e.g. +/- 1 cent) for accurate low-price handling - const minBid = midPrice - spread.val; + 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);