Full methodology
In one paragraph
Around a million raw trade messages from three GPO trading Discord channels flow through a custom Rust parser at roughly 700K messages per second, and the 173,992 clean trade offers that survive filtering are what the solver actually runs on. A multi-phase Rust solver (Dijkstra ratio chain → bundle propagation → per-item balanced windows → soft tier-ordering) turns them into values, anchored at Mythical Fruit Chest = 10,000. Time-decay weights recent trades heavier. Community-voted anchors pin a few items the solver can't triangulate. Every value on the site is derived from trade evidence, with no hand-tuning.
Pipeline, end to end
1 · Scrape
A Discord scraper pulls messages from seven channels (trade channels, booster, premium, certified, plus event channels) and writes gzipped CSVs with timestamp, message id, author id, content, reactions, and reply-to. Filters at scrape time drop obvious offer-marker-only posts ("LF X", "how many for Y", "make me an offer") so they never pollute the pipeline.
2 · Parse (Rust, about 1.4s per million messages)
Discord custom emojis resolve via a three-tier fallback: exact emoji ID, then emoji name (case-insensitive, underscore-tolerant), then a 1,100-entry text-alias table mapping community shorthands (pcc, chakram, pchak, lih, sos, and so on) to canonical names. Same-server emojis and cloned emojis from other servers both resolve.
A compound resolver (about 300 rules) assembles multi-emoji items: Exalted + Wings becomes Exalted Cupid Queen's Wings, Legendary + Fish Bait becomes Legendary Fish Bait, Kraken + Blade + Azure becomes Kraken Blade (Azure), Max + Legendary becomes max-stack Legendary Fish Bait. Descriptor prefixes (Blessed, Exalted, Anomalyzed, Prestige, Holly, True, Demon) combine with entities (Cupid, Roger, Stark, Iceborn, Sunken) and suffixes (Wings, Outfit, Scarf, Katana, Blade) to form item names.
Two dedupe layers kill bump spam: exact message-id dedupe (duplicates across scrapes) and a 60-second bump filter (same author, same content within a minute counts once), plus a per-user-per-day cap (each author/content pair counts once per calendar day).
3 · Scam filter
Pure 1:1 same-quantity no-mod trades are dropped from the ratio graph entirely. Cross-tier 1:1s like "1 PCC for 1 SGC" are almost always scam lowballs; dropping them protects the Dijkstra math below. Cross-item ratio evidence still flows through multi-quantity trades and bundles.
4 · Ratio chain (Dijkstra)
Anchor: Mythical Fruit Chest = 10,000. Every trade between two distinct items is bucketed by its rounded ratio; the weighted-median ratio (time-decayed and trust-weighted) becomes the graph edge weight. Dijkstra finds the max-bottleneck (highest-evidence) path from the anchor to each item, computing each item's value by multiplying ratios along the chosen path.
5 · Bundle propagation
Multi-item trades like "PCC for PFL + PWE" say "1 PCC equals the sum of PFL and PWE values." For each item, we collect every bundle where it appears alone on one side, compute each bundle's implied value, and take the weighted median. Minimum evidence threshold: 10 bundle-weight for well-traded items, 5 for Collectables.
6 · Iterative re-valuation
Bundle values depend on other item values, which depend on other bundles. We iterate with 35% EMA damping and a 30% per-round step cap, up to 10 rounds or until the max per-item change is under 3%. The damping kills the oscillation that otherwise appears across 173,992 interdependent trades.
7 · Per-item balanced windows
Each item uses up to 500 most-recent trades (250 on the offer side, 250 on the want side) as its own evidence pool. This is the structural fix for dominance-by-volume: without it, high-volume items drown out niche items whose bundles get outnumbered. With it, even rarely-traded items get their own representative window.
For items with at least 40 weight of bundle evidence, we use the 40th percentile instead of the median, to guard against a handful of "flex offer" bundles (someone posting an aspirational ask) skewing the median upward. Thin-evidence items still use the 50th percentile.
Aggregate clamp: items can't move more than 1.6× up or down from their pre-phase value in one solver run, which prevents runaway amplification. 25-round iteration with EMA damping and a per-step cap converges cleanly.
8 · Soft tier-ordering penalty
After the math settles, any item whose value exceeds the 75th percentile of any strictly higher tier by more than 20% gets a 10% reduction, in a single pass. New and very-unstable-tier items are exempt, since their values can legitimately swing across tier boundaries during events.
9 · Community anchors
A small number of items have community-validated pins, applied after all solver math. High-weight pins (weight 500 or more) override the solver output exactly; lower-weight pins blend in proportionally. These cover items the solver systematically misprices (fruits trading off-market, brand-new items with no bundle history yet). Anyone can propose one with /suggest in the Discord server; 3 or more yes reactions with a 1.5× yes/no ratio auto-approves.
10 · Time decay
Two half-lives run in parallel:
- Short window, 12-hour half-life. A trade from 12 hours ago is worth half as much as now. Captures intraday shifts.
- Long window, 30-day half-life. A trade from 30 days ago is worth half as much as now. A stable baseline.
Per item, the solver picks whichever window has enough recent evidence to trust. Authors flagged by our wash-detector get 0.5× trust weight so their trades don't dominate.
11 · Post-process refinements
- 1:1 alignment — items stuck at a bad chain estimate get pulled halfway toward a dominant 1:1 partner (50+ trades).
- Demand adjustment — items wanted more than offered get a boost of 3–20%; over-offered items get a matching discount.
- +Adds learning — mod-bearing trades contribute to a learned log-linear adds value per mod type, letting us re-include mod-bearing trades that were previously discarded.
- Wiki injection — rarity, drop rate, location, trade level, and images from the GPO Fandom wiki are merged into each item record.
12 · Daily history backfill
From the first trade date, we replay the solver at 23:59 UTC each day using only trades available up to that day, with "now" set to that cutoff so time decay is computed as the solver would have seen it then. This produces the 60-day per-item time series that powers the changes page and the trend arrows.
Design choices worth understanding
No external value list in the UI
We don't reference any other community price guide. The math stands or falls on the trade evidence alone.
Rust where it matters
The parser and solver are written in Rust. Those are throughput ceilings, not the size of any one run: the parser sustains about 700K messages per second, and the solver stays under about 9 seconds even on inputs far larger than the 173,992 trades an hourly solve actually uses. The rest of the pipeline (post-process, SEO shells, API build) is Python and runs in well under a minute.
Confidence is count-driven
High is 1,000+ trades, medium is 200–999, low is under 200. A low-confidence badge means the number is a directional estimate; treat the range as the real answer.
Community anchors are a last resort
We add anchors sparingly, only when trade data systematically diverges from consensus. Most items (90%+) are pure solver output.
Caveats
- These are trade offerings, not closed deals. The math weights what the community asks, not what clears.
- Exclusives (Marine Cap, top Valkyries) trade rarely; holders don't post. Public offer data under-samples their true market value, hence the low-data flag.
- Brand-new items swing hard for the first two weeks; the item history chart shows the trajectory.
- "+Adds" at the prestige tier can represent another full prestige item's worth of fillers. The learned adds model handles this on average, but individual trades carry that ambiguity.
See also: About · Confidence bands explained · FAQ · Bot commands · API (items.json)