feat(trading): show rank table in dispatch strategy tooltip (#6078)

This commit is contained in:
m.ray 2024-03-27 10:02:46 +00:00 committed by GitHub
parent 5f8c725c5b
commit bfa6be4c1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 259 additions and 6 deletions

View File

@ -0,0 +1,41 @@
import { usePayoutPerRank } from './rank-table';
// Test case as per https://docs.google.com/spreadsheets/d/1RpWKnGEf4eYMxjI-feauRa9vWz3pNGdP05ejQrwWatg/
describe('usePayoutPerRank', () => {
it('should return payouts per person', () => {
const rankTable = [
{
startRank: 1,
shareRatio: 10,
},
{
startRank: 2,
shareRatio: 5,
},
{
startRank: 5,
shareRatio: 5,
},
{
startRank: 10,
shareRatio: 3,
},
{
startRank: 20,
shareRatio: 0,
},
];
const result = usePayoutPerRank(rankTable);
expect(result).toEqual({
numPayouts: [1, 3, 5, 10, 0],
ratioShares: [10, 15, 25, 30, 0],
payoutsPerTier: [0.125, 0.1875, 0.3125, 0.375, 0],
payoutsPerWinner: [0.125, 0.0625, 0.0625, 0.0375],
payoutsPerWinnerAsPercentage: [12.5, 6.25, 6.25, 3.75],
endRanks: [2, 5, 10, 20],
startRanks: [1, 2, 5, 10, 20],
});
});
});

View File

@ -0,0 +1,184 @@
import type { RankTable } from '@vegaprotocol/types';
import { formatNumber } from '@vegaprotocol/utils';
import { useT } from '../../lib/use-t';
export const RankPayoutTable = ({
rankTable,
}: {
rankTable: (RankTable | null)[] | null;
}) => {
const t = useT();
const { payoutsPerWinnerAsPercentage, numPayouts, endRanks } =
usePayoutPerRank(rankTable);
let isOpenFinalTier = false;
return (
rankTable &&
rankTable.length > 0 && (
<>
<table className="min-w-full divide-y divide-gray-200">
<thead className="">
<tr className="text-right text-xs divide-x">
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('Rank tier')}
</span>
</th>
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('Start rank')}
</span>
</th>
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('End rank')}
</span>
</th>
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('Places paid')}
</span>
</th>
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('Payout')}
</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-500 text-xs">
{rankTable?.map((rank: RankTable | null, tier: number) => {
const endRank = endRanks?.[tier];
const placesPaid = numPayouts?.[tier];
const isFinalTier = tier === rankTable.length - 1;
isOpenFinalTier =
isFinalTier && rank ? rank.shareRatio > 0 : false;
if (isFinalTier && rank?.shareRatio === 0) {
return null;
}
return (
rank && (
<tr
key={`rank-table-row-${tier}`}
className="whitespace-nowrap text-right divide-x p-4"
>
<td>
<span className="flex p-1 justify-end">{tier + 1}</span>
</td>
<td>
<span className="flex p-1 justify-end">
{rank.startRank}
</span>
</td>
<td>
<span className="flex p-1 justify-end">
{endRank || 'TBC*'}
</span>
</td>
<td>
<span className="flex p-1 justify-end">
{placesPaid || 'TBC*'}
</span>
</td>
<td>
<span className="flex p-1 justify-end">
{payoutsPerWinnerAsPercentage?.[tier]
? `${formatNumber(
payoutsPerWinnerAsPercentage[tier],
2
)} %`
: 'TBC*'}
</span>
</td>
</tr>
)
);
})}
</tbody>
</table>
<p>
{isOpenFinalTier &&
t(
'*Note: Final tier will payout to all game participants, therefore all payout estimates will lower depending on how many participants there are.'
)}
</p>
</>
)
);
};
// Use rank table to calculate payouts per rank
// as per https://docs.google.com/spreadsheets/d/1RpWKnGEf4eYMxjI-feauRa9vWz3pNGdP05ejQrwWatg/
export const usePayoutPerRank = (
rankTable?: (RankTable | null)[] | null
): {
numPayouts?: number[];
startRanks?: number[];
endRanks?: number[];
ratioShares?: number[];
payoutsPerTier?: number[];
payoutsPerWinner?: number[];
payoutsPerWinnerAsPercentage?: number[];
} => {
if (!rankTable) return {};
const numPayouts: number[] = [],
ratioShares: number[] = [],
startRanks: number[] = [],
endRanks: number[] = [],
payoutsPerTier: number[] = [],
payoutsPerWinner: number[] = [],
payoutsPerWinnerAsPercentage: number[] = [];
let totalRatio = 0;
// We have to work out how many ppl in each bucket to get the total for the ratio
rankTable.forEach((rank, tier) => {
const startRank = rank?.startRank;
startRank && startRanks.push(startRank);
const endRank =
tier < rankTable.length - 1 ? rankTable[tier + 1]?.startRank : undefined;
endRank && endRanks.push(endRank);
const numPayout = endRank && startRank ? endRank - startRank : 0;
numPayouts.push(numPayout);
const shareRatio = rank?.shareRatio;
const ratioShare = shareRatio ? shareRatio * numPayout : 0;
ratioShares.push(ratioShare);
totalRatio += ratioShare;
});
rankTable.forEach((_rank, tier) => {
const ratioShare = ratioShares[tier];
// Then we multiply back out to get the total payout per tier
const payoutPerTierValue = totalRatio ? ratioShare / totalRatio : 0;
payoutsPerTier.push(payoutPerTierValue);
const numPayout = numPayouts[tier];
// And then divide by the payouts to get each individual winner payout
const payoutPerWinnerValue =
numPayout && payoutPerTierValue
? payoutPerTierValue / numPayout
: undefined;
payoutPerWinnerValue && payoutsPerWinner.push(payoutPerWinnerValue);
payoutPerWinnerValue &&
payoutsPerWinnerAsPercentage.push(payoutPerWinnerValue * 100);
});
return {
numPayouts,
ratioShares,
startRanks,
endRanks,
payoutsPerTier,
payoutsPerWinner,
payoutsPerWinnerAsPercentage,
};
};

View File

@ -33,6 +33,7 @@ import { type EnrichedRewardTransfer } from '../../lib/hooks/use-rewards';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { useTWAPQuery } from '../../lib/hooks/__generated__/Rewards'; import { useTWAPQuery } from '../../lib/hooks/__generated__/Rewards';
import { RankPayoutTable } from './rank-table';
const Tick = () => ( const Tick = () => (
<VegaIcon <VegaIcon
@ -92,6 +93,7 @@ const RewardCard = ({
gameId?: string | null; gameId?: string | null;
}) => { }) => {
const t = useT(); const t = useT();
return ( return (
<div> <div>
<div <div
@ -135,11 +137,29 @@ const RewardCard = ({
{/** DISTRIBUTION STRATEGY */} {/** DISTRIBUTION STRATEGY */}
<Tooltip <Tooltip
description={t( description={
DistributionStrategyDescriptionMapping[ <div className="flex flex-col gap-4">
dispatchStrategy.distributionStrategy <p>
] {t(
)} DistributionStrategyDescriptionMapping[
dispatchStrategy.distributionStrategy
]
)}
.
</p>
<p>
{dispatchStrategy.rankTable &&
t(
'Payout percentages are base estimates assuming no individual reward multipliers are active. If users in teams have active multipliers, the reward amounts may vary.'
)}
</p>
{dispatchStrategy.rankTable && (
<RankPayoutTable rankTable={dispatchStrategy.rankTable} />
)}
</div>
}
underline={true} underline={true}
> >
<span className="text-xs" data-testid="distribution-strategy"> <span className="text-xs" data-testid="distribution-strategy">

View File

@ -2,6 +2,7 @@
"(Combined set volume {{runningVolume}} over last {{epochs}} epochs)": "(Combined set volume {{runningVolume}} over last {{epochs}} epochs)", "(Combined set volume {{runningVolume}} over last {{epochs}} epochs)": "(Combined set volume {{runningVolume}} over last {{epochs}} epochs)",
"(Created at: {{createdAt}})": "(Created at: {{createdAt}})", "(Created at: {{createdAt}})": "(Created at: {{createdAt}})",
"(Tier {{tier}} as of last epoch)": "(Tier {{tier}} as of last epoch)", "(Tier {{tier}} as of last epoch)": "(Tier {{tier}} as of last epoch)",
"*Note: Final tier will payout to all game participants, therefore all payout estimates will lower depending on how many participants there are.": "*Note: Final tier will payout to all game participants, therefore all payout estimates will lower depending on how many participants there are.",
"24h vol": "24h vol", "24h vol": "24h vol",
"24h volume": "24h volume", "24h volume": "24h volume",
"A percentage of commission earned by the referrer": "A percentage of commission earned by the referrer", "A percentage of commission earned by the referrer": "A percentage of commission earned by the referrer",
@ -108,6 +109,7 @@
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass", "Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
"End time": "End time", "End time": "End time",
"Ends in": "Ends in", "Ends in": "Ends in",
"End rank": "End rank",
"Entity scope": "Entity scope", "Entity scope": "Entity scope",
"Environment not configured": "Environment not configured", "Environment not configured": "Environment not configured",
"Epoch": "Epoch", "Epoch": "Epoch",
@ -275,6 +277,7 @@
"Page not found": "Page not found", "Page not found": "Page not found",
"Parent market": "Parent market", "Parent market": "Parent market",
"Parent of a market": "Parent of a market", "Parent of a market": "Parent of a market",
"Payout": "Payout",
"Pennant": "Pennant", "Pennant": "Pennant",
"Perpetuals": "Perpetuals", "Perpetuals": "Perpetuals",
"Please choose another market from the <0>market list</0>": "Please choose another market from the <0>market list</0>", "Please choose another market from the <0>market list</0>": "Please choose another market from the <0>market list</0>",
@ -294,6 +297,7 @@
"Public key allow list": "Public key allow list", "Public key allow list": "Public key allow list",
"Purpose built proof of stake blockchain": "Purpose built proof of stake blockchain", "Purpose built proof of stake blockchain": "Purpose built proof of stake blockchain",
"Rank": "Rank", "Rank": "Rank",
"Rank tier": "Rank tier",
"Read the terms": "Read the terms", "Read the terms": "Read the terms",
"Ready to trade": "Ready to trade", "Ready to trade": "Ready to trade",
"Ready to trade with real funds? <0>Switch to Mainnet</0>": "Ready to trade with real funds? <0>Switch to Mainnet</0>", "Ready to trade with real funds? <0>Switch to Mainnet</0>": "Ready to trade with real funds? <0>Switch to Mainnet</0>",
@ -323,6 +327,7 @@
"Rewards history": "Rewards history", "Rewards history": "Rewards history",
"Rewards multipliers": "Rewards multipliers", "Rewards multipliers": "Rewards multipliers",
"Rewards paid out": "Rewards paid out", "Rewards paid out": "Rewards paid out",
"Rewards funded using the rank strategy": "Rewards funded using the rank strategy",
"Reward will be capped at {{capRewardFeeMultiple}} X of taker fees paid in the epoch": "Reward will be capped at {{capRewardFeeMultiple}} X of taker fees paid in the epoch", "Reward will be capped at {{capRewardFeeMultiple}} X of taker fees paid in the epoch": "Reward will be capped at {{capRewardFeeMultiple}} X of taker fees paid in the epoch",
"SCCR": "SCCR", "SCCR": "SCCR",
"Search": "Search", "Search": "Search",
@ -337,12 +342,14 @@
"Settlement price": "Settlement price", "Settlement price": "Settlement price",
"Share data": "Share data", "Share data": "Share data",
"Share usage data": "Share usage data", "Share usage data": "Share usage data",
"Share ratio": "Share ratio",
"Show closed markets": "Show closed markets", "Show closed markets": "Show closed markets",
"Solo team / lone wolf": "Solo team / lone wolf", "Solo team / lone wolf": "Solo team / lone wolf",
"Something went wrong": "Something went wrong", "Something went wrong": "Something went wrong",
"Spot": "Spot", "Spot": "Spot",
"Spot markets coming soon.": "Spot markets coming soon.", "Spot markets coming soon.": "Spot markets coming soon.",
"Spread": "Spread", "Spread": "Spread",
"Start rank": "Start rank",
"Stake a minimum of {{minimumStakedTokens}} $VEGA tokens": "Stake a minimum of {{minimumStakedTokens}} $VEGA tokens", "Stake a minimum of {{minimumStakedTokens}} $VEGA tokens": "Stake a minimum of {{minimumStakedTokens}} $VEGA tokens",
"Stake some $VEGA now": "Stake some $VEGA now", "Stake some $VEGA now": "Stake some $VEGA now",
"Staked VEGA": "Staked VEGA", "Staked VEGA": "Staked VEGA",
@ -502,6 +509,7 @@
"{{reward}}x": "{{reward}}x", "{{reward}}x": "{{reward}}x",
"Private": "Private", "Private": "Private",
"Public": "Public", "Public": "Public",
"Places paid": "Places paid",
"Copy this page url.": "Copy this page url.", "Copy this page url.": "Copy this page url.",
"Visit the team's page.": "Visit the team's page." "Visit the team's page.": "Visit the team's page."
} }

View File

@ -713,7 +713,7 @@ export const IndividualScopeMapping: { [e in IndividualScope]: string } = {
export const IndividualScopeDescriptionMapping: { export const IndividualScopeDescriptionMapping: {
[e in IndividualScope]: string; [e in IndividualScope]: string;
} = { } = {
INDIVIDUAL_SCOPE_ALL: 'All parties are eligble', INDIVIDUAL_SCOPE_ALL: 'All parties are eligible',
INDIVIDUAL_SCOPE_IN_TEAM: 'Parties in teams are eligible', INDIVIDUAL_SCOPE_IN_TEAM: 'Parties in teams are eligible',
INDIVIDUAL_SCOPE_NOT_IN_TEAM: 'Only parties not in teams are eligible', INDIVIDUAL_SCOPE_NOT_IN_TEAM: 'Only parties not in teams are eligible',
}; };