metafide-surge
Version:
Metafide Surge Game Computation
358 lines (357 loc) • 17 kB
JavaScript
"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;