Measuring Campaign ROI
The two numbers that survive a retrospective — cost per acquired trader and volume retention — how to instrument both, and why you cannot do it afterwards.
Almost every competition retrospective leads with peak volume, which is the least informative number available: it measures the size of the pool and the aggressiveness of the field, not the value of either. Two numbers actually tell you whether the money worked. Both are computable, and both have to be set up before the campaign starts.
Number one: cost per acquired trader
Pool ÷ wallets that traded during the campaign and traded again after it settled.
Every part of that definition is load-bearing.
Wallets, not registrations. Registration is free and costs a signature. It measures how easy your form is. A campaign can register thousands and acquire nobody.
Traded during, and again after. The post-settlement trade is the whole definition. Without it you are counting people who were paid to appear once — which is a real thing you bought, just not the thing the budget was approved for.
After settlement, not after the end date. Competitions hold a settlement window past the end so the last day's volume finalises before anything is paid. A wallet that trades during that window is still inside the incentive. The cohort clock starts when the money is settled, not when the clock hit zero.
The denominator will be much smaller than your participation count. That is the point. A number that looks bad next to registrations is usually the first true number a growth team has seen.
Number two: volume retention
Share of campaign-period volume still present weeks after settlement.
Some proportion of campaign volume is mercenary by construction and leaves with the pool. That is expected, not a scandal. The ratio that remains is the campaign's result.
Measure it as a cohort, not as a venue total. Take the wallets that traded during the campaign, and track their volume in the two-to-four weeks after settlement against the volume they did during it. Venue-wide totals move for a dozen reasons that have nothing to do with your campaign; a cohort restricted to the wallets you actually paid does not.
Report both this and its ugly sibling: cost per dollar of counted volume, using the volume the rules actually counted rather than raw traded volume. The gap between counted and raw is your anti-abuse machinery working, and publishing both makes it visible instead of buried.
What you need stored to compute either
Neither number can be reconstructed from a final leaderboard. A leaderboard is a rolled-up total per wallet at one instant; retention is a time series, and the acquisition cohort needs a "before" boundary the rollup has thrown away.
What is sufficient is a per-participation, per-day volume row. On Voltrade each row carries:
| Field | What it is |
|---|---|
date | The day bucket — a rolling 24h window anchored to the campaign's start time, so "day 3" means the third 24h since start, not a calendar day |
volumeUsd | Counted volume: the day's volume after the per-day cap |
rawVolumeUsd | Uncapped volume as the venue reported it |
rawVxpPoints / vxpPoints | Points pre-cap and post-cap |
overDailyCap | Whether the cap bound that day |
closed | Whether the day is frozen past its settlement buffer |
One row per participation per day, uniquely keyed on the pair. That shape gives you all three numbers directly: counted-vs-raw is two columns, the acquisition cohort is a filter on the campaign's day range, and retention is the same wallets' rows after it.
Two details in that table matter more than they look. Day buckets are anchored to campaign start, not midnight UTC — a comp starting at 14:00 rolls its day at 14:00, and every cap, raffle draw and per-day aggregate uses that boundary. Any external analysis that re-buckets by calendar day will disagree with the leaderboard, and the leaderboard is what got paid. And closed is what makes a number final: an open day can still move as late data arrives, so a retrospective run against open rows is a draft.
Instrumenting it through the API
If you are pulling this into your own warehouse, three endpoints cover it.
GET /competitions/{slug}/leaderboard returns ranked participants and, per entry, both volumes:
rank, walletAddress, displayName, traderSlug, totalPoints,
totalVolumeUsd, uncappedTotalVolumeUsd, totalPnlUsd, totalPnlPercent,
totalLotteryRewardUsd, challengesCompleted, twitterConnected,
tradeCount, projectedRewardUsd
totalVolumeUsd is the capped volume that points are computed from; uncappedTotalVolumeUsd is the raw traded volume. Cost per dollar of counted volume uses the first; the gap between them is the second number in your abuse report. Query it with by=points or by=volume, paginated up to 1000 per page.
GET /competitions/{slug} gives the rules the numbers have to be read against — reward rules, eligibility, scoring, lottery, payout rail. A cost-per-trader figure without the qualifying minimum and daily cap beside it is not interpretable.
GET /stats returns your key's own attribution: registrations and unique users, active competitions, total volume and points earned by users you referred, and a per-competition breakdown. That is the top of the funnel — useful as a denominator, never as the acquisition number itself.
Snapshot the leaderboard on a schedule while the competition runs, not once at the end. The endpoint returns the current state; the time series is yours to keep.
The honest caveat
You can only measure any of this if you set it up beforehand.
That is not a process complaint, it is arithmetic. The acquisition cohort needs to know who traded before settlement and who traded after — two populations that only exist if something was recording during the window. Retention needs a per-wallet baseline from the campaign period to divide by. A screenshot of the final leaderboard contains neither.
The practical consequence for a venue: decide your two numbers before you fund the pool, confirm the data that computes them is being stored per-day and per-wallet, and take the first snapshot on day one. Retrofitting is not partially possible — it is not possible.
What not to put on the report
- Peak volume. Most quoted, least informative.
- Registrations as acquisitions. Free to produce, therefore meaningless as a result.
- Venue-wide volume before and after. Confounded by everything else that happened.
- Anything from a campaign whose rules changed mid-flight. Changing rules mid-campaign destroys both the credibility and the comparability. If something has to change, the campaign is over and a new one starts.
For the full growth-side playbook these two numbers sit inside, read user acquisition for perp DEXs; for the rules that determine whether counted volume is worth anything at all, wash-trading resistance in volume campaigns. Endpoint shapes are in the API docs.
Keep reading
- User Acquisition for Perp DEXs: The Campaign Playbook
For venue growth teams — campaign types, budget allocation, measuring cost per acquired trader and volume retention, and the mistakes that hide a bad result.
- Reward Rails Compared: Manual, Merkl and Partner-Direct
Three ways to get prize money to winners — operator transfers, a Merkle-root claim contract, or the partner paying directly. Trust, gas, claim UX and auditability.
- Wash-Trading Resistance in Volume Campaigns
The actual defenses — per-day counted-volume caps, minimum qualifying thresholds, pro-rata economics, flat-start rules, and raw volume shown beside counted.
Every trade is a competition
Join a live volume competition or PnL challenge across top venues — or launch your own in minutes.