UNPKG

metafide-surge

Version:

Metafide Surge Game Computation

358 lines (357 loc) 17 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateRangePotDistribution = exports.calculateSpotPotDistribution = exports.computeSpotPlayerVariance = exports.computeRangePlayerVariance = exports.computeSpotWinnings = exports.computeRangeWinnings = void 0; const utils_1 = require("./utils"); // RANGE CALCULATION /** * Calculates the pot distribution for a game based on players' contributions * and given allocation percentages. * * @param players - List of players participating in the game * @param allocations - Optional custom allocation percentages (defaults to predefined values) * @returns An object containing the breakdown of the total pot into different allocations */ function calculateRangePotDistribution(data, allocations = utils_1.DEFAULT_RANGE_ALLOCATION_PERCENTAGES) { // If there are no players, return an empty result immediately if (data.length === 0) { return { total_pot: 0, metafide_rake: 0, streak_pot_5: 0, streak_pot_10: 0, streak_pot_25: 0, daily_reward_pot: 0, monthly_reward_pot: 0, burn: 0, winning_pot: 0, }; } const players = data.map(utils_1.serializeRangePlayer); const total_pot = players.reduce((sum, player) => sum + (0, utils_1.parseNumeric)(player.f), 0); // Compute all allocations using the provided percentages const metafide_rake = parseFloat((total_pot * allocations.metafide_rake).toFixed(2)); const streak_pot_5 = parseFloat((total_pot * allocations.streak_pot_5).toFixed(2)); const streak_pot_10 = parseFloat((total_pot * allocations.streak_pot_10).toFixed(2)); const streak_pot_25 = parseFloat((total_pot * allocations.streak_pot_25).toFixed(2)); const daily_reward_pot = parseFloat((total_pot * allocations.daily_reward_pot).toFixed(2)); const monthly_reward_pot = parseFloat((total_pot * allocations.monthly_reward_pot).toFixed(2)); const burn = parseFloat((total_pot * allocations.burn).toFixed(2)); // Sum all deductions const totalDeductions = metafide_rake + streak_pot_5 + streak_pot_10 + streak_pot_25 + daily_reward_pot + monthly_reward_pot + burn; // Compute the winning pot (remaining after deductions) const winning_pot = parseFloat((total_pot - totalDeductions).toFixed(2)); // Return a structured breakdown return { total_pot: parseFloat(total_pot.toFixed(2)), metafide_rake, streak_pot_5, streak_pot_10, streak_pot_25, daily_reward_pot, monthly_reward_pot, burn, winning_pot, }; } exports.calculateRangePotDistribution = calculateRangePotDistribution; /** * Computes the variance between players' predicted price ranges and the actual game price range. * * @param players - List of players participating in the game * @param game - The game data including actual price boundaries and timestamps * @returns An array of PlayerVarianceResult containing each player's variance data and total variance sum */ function computeRangePlayerVariance(players, game) { // If there are no players, return an empty result immediately if (players.length === 0) return []; // Prepare a simplified game data object for easy access const rangeGameData = { end_timestamp: Math.floor(new Date(game.t3).getTime() / 1000), high_actual: game.ha, low_actual: game.la, gid: game.gid, // game ID }; // Calculate each player's variance based on their predictions vs actual prices const varianceData = players.map(p => { let total_variance = 0; // Only compute variance if the player's predicted high is above actual high // AND predicted low is below actual low (player's range fully covers the actual range) if ((0, utils_1.parseNumeric)(p.hp) > rangeGameData.high_actual && (0, utils_1.parseNumeric)(p.lp) < rangeGameData.low_actual) { total_variance = Math.pow(Math.max((0, utils_1.parseNumeric)(p.hp) - rangeGameData.high_actual, 0) + Math.max(rangeGameData.low_actual - (0, utils_1.parseNumeric)(p.lp), 0), 1.5); } // Return the basic variance data for this player return Object.assign(Object.assign({}, p), { varianceInput: { high_actual: rangeGameData.high_actual, low_actual: rangeGameData.low_actual, total_variance, // computed variance } }); }); // Compute the total variance across all players const sumTotalVariance = varianceData.reduce((sum, v) => sum + v.varianceInput.total_variance, 0); // Merge the total variance into each player's result and return the final array return varianceData.map(v => (Object.assign(Object.assign({}, v), { varianceInput: { high_actual: v.varianceInput.high_actual, low_actual: v.varianceInput.low_actual, total_variance: v.varianceInput.total_variance, sum_total_variance: parseFloat(sumTotalVariance.toFixed(6)), } }))); } exports.computeRangePlayerVariance = computeRangePlayerVariance; /** * Handles the full computation of player winnings based on their variance and the game's pot distribution. * * @param players - List of players participating in the game * @param games - The game data including actual price boundaries and timestamps * @param distribution - The Range Game Distribution * @returns An object containing detailed winnings per player, highest winnings, and largest returns */ function computeRangeWinnings(data, games, distribution) { // If there are no players, immediately return empty results if (data.length === 0) { return { players: [], streak: { winnings: [], returns: [], }, }; } // Step 1: Compute total pot distribution and player variances const players = data.map(utils_1.serializeRangePlayer); const computedPlayers = computeRangePlayerVariance(players, games); // Step 2: Calculate the total sum of variances const sumTotalVariance = computedPlayers.reduce((sum, player) => sum + player.varianceInput.total_variance, 0); // Step 4: Handle the special case where no player has any variance if (sumTotalVariance === 0) { return { players: computedPlayers.map(player => (Object.assign(Object.assign({}, player), { w: 0, r: 0 }))), streak: { winnings: [], returns: [], }, }; } // Step 5: Precompute the sum of (shareTokensToVariance) for normalization const sumShareTokensToVariance = computedPlayers.reduce((sum, player) => { if (player.varianceInput.total_variance === 0) return sum; // Ignore players with zero variance const shareVariance = player.varianceInput.total_variance / sumTotalVariance; const shareTokens = (0, utils_1.parseNumeric)(player.f) / distribution.total_pot; const shareTokensToVariance = shareTokens / shareVariance; return sum + shareTokensToVariance; }, 0); // Step 6: Compute each player's normalized winnings and percent return const updatedPlayers = computedPlayers.map(player => { if (player.varianceInput.total_variance === 0) { return Object.assign(Object.assign({}, player), { w: 0, r: 0 }); } const shareVariance = player.varianceInput.total_variance / sumTotalVariance; const shareTokens = (0, utils_1.parseNumeric)(player.f) / distribution.total_pot; const shareTokensToVariance = shareTokens / shareVariance; const normalizedTokensToVariance = shareTokensToVariance / sumShareTokensToVariance; // Raw winnings in system's units, then converted down const winningsRaw = normalizedTokensToVariance * distribution.winning_pot; // Percent return relative to player's initial stake const percentReturn = ((winningsRaw - (0, utils_1.parseNumeric)(player.f)) / (0, utils_1.parseNumeric)(player.f)) * 100; const { varianceInput } = player, rest = __rest(player, ["varianceInput"]); return Object.assign(Object.assign({}, rest), { w: winningsRaw, r: percentReturn }); }); // Step 7: Determine the number of top players (2% of total players, rounded up) const totalPlayers = updatedPlayers.length; const topPlayersCount = Math.ceil(totalPlayers * 0.02); // Step 8: Find top players by highest winnings const highestWinnings = [...updatedPlayers] .sort((a, b) => b.w - a.w) // Sort descending by winnings .slice(0, topPlayersCount); // Step 9: Find top players by highest percent return const largestReturns = [...updatedPlayers] .sort((a, b) => b.r - a.r) // Sort descending by percent return .slice(0, topPlayersCount); // Step 10: Return the full computation result return { players: updatedPlayers, streak: { winnings: highestWinnings, returns: largestReturns, }, }; } exports.computeRangeWinnings = computeRangeWinnings; // SPOT COMPUTATION function calculateSpotPotDistribution(data, allocations = utils_1.DEFAULT_SPOT_ALLOCATION_PERCENTAGES) { if (data.length === 0) { return { total_pot: 0, metafide_rake: 0, range_rake: 0, streak_pot_5: 0, streak_pot_10: 0, streak_pot_25: 0, daily_reward_pot: 0, monthly_reward_pot: 0, burn: 0, winning_pot: 0, }; } const players = data.map(utils_1.serializeSpotPlayer); const total_pot = players.reduce((sum, player) => sum + (0, utils_1.parseNumeric)(player.f), 0); const metafide_rake = parseFloat((total_pot * allocations.metafide_rake).toFixed(2)); const streak_pot_5 = parseFloat((total_pot * allocations.streak_pot_5).toFixed(2)); const streak_pot_10 = parseFloat((total_pot * allocations.streak_pot_10).toFixed(2)); const streak_pot_25 = parseFloat((total_pot * allocations.streak_pot_25).toFixed(2)); const daily_reward_pot = parseFloat((total_pot * allocations.daily_reward_pot).toFixed(2)); const monthly_reward_pot = parseFloat((total_pot * allocations.monthly_reward_pot).toFixed(2)); const burn = parseFloat((total_pot * allocations.burn).toFixed(2)); const range_rake = allocations.range_rake ? parseFloat((total_pot * allocations.range_rake).toFixed(2)) : 0; // Sum all deductions including optional range rake const totalDeductions = metafide_rake + streak_pot_5 + streak_pot_10 + streak_pot_25 + daily_reward_pot + monthly_reward_pot + burn + range_rake; const winning_pot = parseFloat((total_pot - totalDeductions).toFixed(2)); return { total_pot: parseFloat(total_pot.toFixed(2)), metafide_rake, streak_pot_5, streak_pot_10, streak_pot_25, daily_reward_pot, monthly_reward_pot, burn, winning_pot, range_rake, }; } exports.calculateSpotPotDistribution = calculateSpotPotDistribution; /** * Computes the spot variance between players' predicted price ranges and the actual game price range. * * @param players - List of players participating in the game * @param game - The game data including actual price boundaries and timestamps * @returns An array of PlayerVarianceResult containing each player's variance data and total variance sum */ function computeSpotPlayerVariance(players, game) { if (players.length === 0) return []; const closedTimestamp = Number(game.t3); // Already in seconds const actualPrice = Number(game.ca); const spotVariance = players.map(player => { const spotTimestamp = Math.floor(Number(player.t) / 1000); const timestampDiff = closedTimestamp - spotTimestamp; const absSpotVar = Math.abs(actualPrice - Number(player.sp)); const tokenMilliseconds = Math.pow(51250 - timestampDiff, 1.25) * Number(player.f); return Object.assign(Object.assign({}, player), { varianceInput: { actual_price: actualPrice, abs_spot_var: absSpotVar, token_milliseconds: tokenMilliseconds, } }); }); const sumAbsSpotVar = spotVariance.reduce((sum, p) => sum + p.varianceInput.abs_spot_var, 0); const sumTokenMilliseconds = spotVariance.reduce((sum, p) => sum + p.varianceInput.token_milliseconds, 0); const shareVarianceData = spotVariance.map(p => { const shareTkmToVar = p.varianceInput.abs_spot_var === 0 || sumAbsSpotVar === 0 ? 0 : p.varianceInput.token_milliseconds / sumTokenMilliseconds / (p.varianceInput.abs_spot_var / sumAbsSpotVar); return Object.assign(Object.assign({}, p), { varianceInput: { actual_price: p.varianceInput.actual_price, abs_spot_var: p.varianceInput.abs_spot_var, token_milliseconds: p.varianceInput.token_milliseconds, share_tkm_to_var: shareTkmToVar, } }); }); const sumShareTkmToVar = shareVarianceData.reduce((sum, p) => sum + Number(p.varianceInput.share_tkm_to_var), 0); return shareVarianceData.map(p => (Object.assign(Object.assign({}, p), { varianceInput: { actual_price: p.varianceInput.actual_price, abs_spot_var: p.varianceInput.abs_spot_var, token_milliseconds: p.varianceInput.token_milliseconds, share_tkm_to_var: p.varianceInput.share_tkm_to_var, sum_share_tkm_to_var: sumShareTkmToVar, } }))); } exports.computeSpotPlayerVariance = computeSpotPlayerVariance; /** * Computes the final spot game results for players, including * winnings, top winners, and highest returns based on their predictions. * * @param data - List of SpotPlayer entries participating in the game * @param game - SpotGame containing game closing data * @param distribution - The SpotGame distribution * @returns An object containing player winnings, highest winnings, and largest returns */ function computeSpotWinnings(data, game, distribution) { // If no players, immediately return empty results if (data.length === 0) { return { players: [], streak: { winnings: [], returns: [], }, }; } // Step 1: Calculate total pot distribution and each player's variance and share based on spot prediction const players = data.map(utils_1.serializeSpotPlayer); const computedPlayers = computeSpotPlayerVariance(players, game); // Step 3: Calculate winnings and percent return for each player const updatedPlayers = computedPlayers.map(player => { // Calculate normalized share of time/variance const shareTotalTimeVariance = player.varianceInput.sum_share_tkm_to_var > 0 ? player.varianceInput.share_tkm_to_var / player.varianceInput.sum_share_tkm_to_var : 0; const winningsRaw = shareTotalTimeVariance * distribution.winning_pot; const percentReturn = (0, utils_1.parseNumeric)(player.f) > 0 ? ((winningsRaw - (0, utils_1.parseNumeric)(player.f)) / (0, utils_1.parseNumeric)(player.f)) * 100 : 0; const { varianceInput } = player, rest = __rest(player, ["varianceInput"]); return Object.assign(Object.assign({}, rest), { w: winningsRaw, r: percentReturn }); }); // Step 4: Identify top 2% of players by winnings const totalPlayers = updatedPlayers.length; const topPlayersCount = Math.max(1, Math.ceil(totalPlayers * 0.02)); // Always at least 1 player const highestWinnings = [...updatedPlayers] .sort((a, b) => b.w - a.w) .slice(0, topPlayersCount); // Step 5: Identify top 2% of players by percent return const largestReturns = [...updatedPlayers] .sort((a, b) => b.r - a.r) .slice(0, topPlayersCount); // Step 6: Return results return { players: updatedPlayers, streak: { winnings: highestWinnings, returns: largestReturns, }, }; } exports.computeSpotWinnings = computeSpotWinnings;