The Verified Leaderboard: How Pro Trading Journal Ranks Real Execution, Not Screenshots
Only exchange-synced, closed trades count toward Pro Trading Journal's leaderboard. Here's exactly how PTJ Score, the eligibility floor, and reciprocal privacy work, straight from the code that runs it.
The Problem With Every Other Trading Leaderboard
Anyone can screenshot a green P&L. Nobody can verify it. That's the entire integrity problem with public trading leaderboards, and it's why most of them are worthless as anything other than entertainment.
Pro Trading Journal's leaderboard, at /dashboard/leaderboard, is built around one rule meant to fix that โ and the rule is expensive. It throws out most of the trades sitting in the app.
computeCohort(), the function that assembles who's even eligible to be ranked, filters trades with eq(trades.sourceType, 'exchange'), isNotNull(sourceExchange), isNotNull(sourceConnectionId), and isNotNull(trades.exit). In plain terms: a trade only counts if it came from a connected exchange account, synced automatically, and is fully closed. A trade you typed in by hand โ even a perfectly accurate PSX fill you logged the same day โ never touches the leaderboard. Not because of a policy someone has to remember to enforce. Because the query never selects it.
The table says it in one line, right above the rankings:
"Rankings use exchange-synced trades only โ no manual entries count."
Everything else in this post โ the score, the eligibility floor, the privacy model โ sits on top of that one filter.
Getting On the Board Is Opt-In, Not Automatic
Connecting an exchange doesn't put you on the leaderboard. Nothing does, until you turn it on yourself.
The opt-in lives on a profile panel on the leaderboard page itself (and on a compact rank widget elsewhere in the dashboard). Flip "Show me on the leaderboard" and you're prompted for a display name โ 3 to 20 characters, letters, numbers, spaces, underscores and hyphens only, and unique across every user on the platform. Pick one that's taken and the server catches the Postgres unique-constraint violation and hands back "That name is taken โ pick another." rather than a raw database error.
Two more constraints are enforced server-side before the toggle does anything:
- You need Pro. The same server action that saves your profile,
updateLeaderboardProfile, checks real billing status โ not a client-side flag โ and refuses to opt you in otherwise. - You need a connected exchange. Try to opt in without one and the action returns "Connect an exchange to join โ only synced trades are ranked."
Connect an exchange -> Trades sync automatically -> Turn on "Show me on the leaderboard" -> Pick a unique name -> Cohort recomputes (every 5 minutes) -> Rank appears
Saving your profile also triggers a targeted cache invalidation (updateTag on the leaderboard's cache tag), so you see your own change reflected immediately instead of waiting out the normal refresh window โ read-your-own-writes, even though the board underneath is cached for everyone else.
The Eligibility Floor: 10 Trades, $5,000 Volume
Opting in doesn't guarantee a rank. score.ts sets a floor: 10 closed, synced trades and $5,000 in volume (summed capital risked) within the active window. Fall short of either and you're dropped from ranking before scoring even runs โ rankEntries filters on isEligible up front.
You're not hidden from yourself, though. The page shows you exactly where you stand either way:
- Below the floor: "You're in โ keep trading. You need {minTrades}+ synced trades (you have {trades}) and ${minVolumeUsd}+ volume to be ranked."
- At or above it: "You're ranked #{rank} of {totalRanked}."
- Never opted in: "You're not on the leaderboard yet. Turn on the switch below to join."
The floor exists for the obvious reason โ three lucky trades on $200 of capital shouldn't be able to sit at the top of a board meant to measure sustained execution.
PTJ Score: Four Percentile Ranks, Not Four Raw Numbers
This is the part that took the most care to get right, because raw numbers lie in a leaderboard context. A 40% return means something completely different next to a $500 account than next to a $500,000 one, and a single outsized winning trade can make an otherwise mediocre trader's return percentage look like the best on the board.
So score.ts doesn't rank on raw values. It ranks on percentile position within the eligible cohort, for four separate legs, then combines them with fixed weights that sum to 1.00:
| Component | Weight |
|---|---|
| Return % | 0.35 |
| Win rate | 0.25 |
| Consistency (profit factor) | 0.25 |
| Volume | 0.15 |
"Consistency" is gross profit divided by gross loss โ the classic profit factor โ capped at 5x before it's percentile-ranked, specifically so a trader with zero losing trades (an undefined or infinite profit factor) can't break the math by dividing by zero. Each leg is converted to a percentile with the same formula: (count strictly below you + 0.5 ร count tied with you) / n ร 100. That 0.5 tie-breaking term matters at the edges โ it's also why a cohort of exactly one person scores a flat 50, not 100.
The four percentiles are blended into a single 0โ100 score, rounded to one decimal. The tooltip on the page explains it the same way to users:
"PTJ Score (0โ100) ranks you against everyone else on four parts: return %, win rate, consistency (profit factor) and volume. Each is percentile-ranked, so one lucky trade can't dominate."
A Concrete Example From Our Own Test Suite
tests/features/leaderboard/score.test.ts locks this weighting in place with a small fixture cohort of four archetypes โ not real users, just enough variance to prove the math behaves as intended:
| Archetype | Profile | PTJ Score |
|---|---|---|
| Balanced | above-average across all four legs, no single stat carries it | โ 68.8 |
| Whale | largest volume in the cohort, average everywhere else | โ 51.3 |
| Gambler | best return %, weak win rate and consistency | โ 42.5 |
| Grinder | best win rate, tiny volume | โ 37.5 |
The balanced trader wins by more than 17 points over the next closest, despite not leading in a single individual column. That's exactly the outcome the 35/25/25/15 weighting is designed to produce โ a specialist who's spectacular on one axis and unremarkable on the other three shouldn't outrank someone who's solidly good at all four. (Related read: why win rate alone doesn't tell you if a system makes money โ the same "one metric isn't the whole story" logic that shaped this score.)
Reciprocal Privacy: You Only See What You're Willing to Show
Rank, name, PTJ Score, return %, win rate, and trade count are public to anyone who can see the board. Two fields aren't: volume in dollars, and P&L in dollars.
Those two are gated by what the code itself calls, in a comment left directly above the function, "reciprocal privacy":
"Reciprocal privacy projection. Return % and win rate are always public; volume $ and P&L $ are visible in a given cell only when BOTH the viewer and that row's user share money."
The rule is literally moneyVisible = viewerSharesMoney && rowSharesMoney โ an AND, not an OR, not a one-way mirror. And it isn't enforced by hiding a number behind CSS. The hidden fields are set to null on the server before the row is ever assembled into a response โ the comment in the code is explicit about why: "Hidden money is never placed on the public DTO, so it can't leak to the client." There's nothing in your browser's network tab to inspect, because the number was never sent.
Row assembled server-side -> Viewer shares $ AND that trader shares $? -> Yes: dollar fields attached -> No: dollar fields set to null -> Row leaves the server
The toggle that controls your side of this is worded plainly on the profile panel:
"Share volume & dollar P&L" โ "On: others see your $ and you see theirs. Off: percentages only โ you see just %/win-rate and so does everyone looking at you."
That last clause is the part worth sitting with: reciprocity is strict and symmetric. If you leave money-sharing off, you don't see anyone else's dollar figures โ including, per the same projection function applied uniformly to every row, your own. It's not a partial redaction where you get to peek and everyone else doesn't. Locked cells in the table show a lock icon with the tooltip "Hidden โ this trader shares percentages only" instead of a number, for both directions of the exchange.
Two Windows, Top 20, Refreshed Every Five Minutes
The board has two time scopes, toggled client-side: 30-Day, a rolling window from a UTC cutoff, and All-Time, no cutoff at all. Switching between them re-ranks against a different trade set, so your position โ and who's above you โ can move a lot between the two.
Twenty rows are fetched server-side (TOP_N = 20), with the UI additionally offering a Top 10 / Top 20 display toggle over that same set. If you're ranked but outside whichever slice is showing, your own row gets pinned in separately rather than just disappearing off the bottom.
The cohort computation โ the expensive part, aggregating every eligible user's synced trades โ is cached for five minutes (unstable_cache, 300-second revalidation) rather than recomputed on every page load. The privacy projection described above is not cached; it runs fresh per viewer on every request, on top of the cached cohort, which is the only way the reciprocal rule can correctly depend on who's currently looking at the page.
What We Haven't Overpromised
A few things worth stating plainly rather than glossing over, in keeping with how we'd want a competitor's leaderboard explained to us:
The purchase button isn't guaranteed to be a purchase button. The leaderboard page enforces Pro at the code level โ Clerk sign-in required, then a real billing check (hasPro) before you see anything but a paywall. But whether the paywall's "Get Pro" link is a working checkout or a waitlist signup depends on a separate condition, arePaymentsLive(), which checks that payments are configured and the Solana treasury is running on mainnet. When that's not yet true, the same screen shows a founding-waitlist form instead โ "Crypto checkout is being prepared. Join the founding list and we'll email you when Pro payments open." The gate is real either way; only the door on the other side of it changes. (We've written separately about how that payment flow works once it's live.)
You won't find this page from a Google search. The route ships with robots: { index: false, follow: false } on purpose. It's a signed-in, personalized, Pro-gated feature, not a marketing page, and it isn't meant to be crawled or indexed.
The pure math is tested; the assembly around it is read, not pinned. The percentile and weighting logic in score.ts has a dedicated, passing unit test suite. The database aggregation and the privacy projection that sit around that math were verified by reading the source directly โ there isn't yet a dedicated integration test locking the full pipeline in place end to end. Worth knowing if you're relying on this post as a spec rather than a description of current behavior.
Nothing here changes what ships today. It's the same instinct behind the backtest we threw away on the Pro Algo page โ a shipped feature is more trustworthy when you can also see exactly where its edges are.
Try It
If you're syncing a real exchange account already, the leaderboard is a way to find out where your execution actually stands against other synced traders โ not against your own screenshots. Head to /dashboard/leaderboard, and if you're not Pro yet, compare plans on the pricing page. Connect an exchange, let ten trades close, opt in when you're ready โ and choose for yourself whether the dollar signs come with it.
Recommended reading
Related guides
How Pro Trading Journal Payments Work: USDC on Solana, No Card, No Wallet Connection
Pro Trading Journal now accepts USDC on Solana for Pro. No card, no subscription, no wallet connection โ a QR code, an address, and an exact amount. Here's exactly how it works and why we built it this way.
The Multi-Market Dashboard: Scoping Your P&L Across Crypto, US Stocks, and PSX Without Faking the Math
A scope selector that narrows your dashboard to one market at a time โ and the honest math problem it created: what does "return" mean when your account has only one starting-capital figure to split three ways?
Community Signals & the PTJ Algo Radar: How We Verify Every Call
Two systems on one Pro-gated page: an algo radar that hides its numbers until 100 trades back them up, and a community call feed where a daily cron replays open signals against real candles to earn the verified badge.
Ready to review your trades with a crypto trading journal?
Turn this guide into a repeatable review habit with Pro Trading Journal, a crypto trading journal app for tracking trades, R-multiples, equity curve, and trading psychology. Starter includes 50 new trades every calendar month with no expiry.
Start Your Free Journal