Grand Exchange “GE” Terminal

Market intelligence for a 4,700-item game economy.

Scope

Signal engine, verdict system, risk grading, P&L, Discord alerts

Context

Personal tool, built from zero Python experience

Delivery

Locally-deployed Python/Streamlit app

Status

Active, 18+ versions, 2026

GE Terminal Watchlist tab showing 6 trade cards
Cards display price sparklines, zone labels (DUMP/ACCUMULATE/Trim/Nibble), buy and sell targets, and A/B/F grade badges

Context

The Grand Exchange is Old School RuneScape’s player-driven commodities market: 4,700+ items, real volatility, visible volume, and nothing but a raw price API to read it with. Grand Exchange “GE” Terminal is the market intelligence terminal I built and run daily, with a layered signal engine, verdicts that explain themselves, and an override that keeps a human in the loop.

4,700+

items in the market

4

price timeframes read per candidate

2,400+

lines of custom Python

18+

versions, running daily

1

user (self)

Problem

Flipping items on the Grand Exchange is a decision-making problem under noisy data. Prices spike and crash hourly, some items dump on predictable cycles, and the wiki’s raw price API tells you what happened without telling you what it means.

GE Terminal Actions tab
Priority-sorted trade queue showing ‘SELL — Grade A / Grimy toadflax’ at 1,966 gp sell price, 3,401 units, realizing +6.70M total gain

Every trade came down to the same question

“Is this signal real, or am I about to chase a correction?”

Solution

GE Terminal answers that with a layered signal engine. It pulls live and historical prices across four timeframes, scores movers on momentum and volume, then runs each candidate through graded buy and sell checklists before anything surfaces as an action.

Grading the market before looking at the signal

A momentum scanner’s failure mode is enthusiasm. Every violent move looks like an opportunity, and the biggest movers are almost always the worst trades; thin, wide-spread items where the move is two people trading at once. So the market gets graded first: range width, volatility, volume, spread, sample depth, each subtracting from a 100-point start. Below 45, and an item is graded with an F, and with the Trade grade ≥ B filter on, F never becomes an action.

Live stress test result: items crashed 50–61% in one hour, all graded F, and none surfaced to the Actions tab

The extreme case is a hard stop. If price breaks below its own 7-day floor, the zones the signal was derived from stop being valid, so the label is replaced with BROKEN and the grade takes a −60 penalty, enough to fail an item on its own. It fires rarely, because once an item crashes, the floor moves down to meet it. Most suppression happens in the grading.

DECISION FORK 1

Surface every mover, or suppress by default?

The obvious build surfaces the biggest movers. That’s what a scanner is for, and it’s what makes the tool feel alive but it’s also how you train yourself to ignore your own alerts. The market gets graded before the signal does, and what reaches Discord clears a floor I set, plus anything I hold or watch.

Two scales, one word

Two grade systems run in parallel, measuring different things. Trade grade asks whether the item is worth trading at all: range width, volatility, volume, spread, sample depth. It’s a property of the market and it barely moves hour to hour. Signal grade asks whether right now is the moment: three checklist questions, rescored on every refresh. Is this a dip rather than a trend? Is there room to move? Are buyers in control?

Discord GE Alerts bot message: SELL signal for Emerald at 598 gp, 74% confidence, 52,694 vol/h, Trade A grade, 2 of 3 checks passed
tier_label() resolution matrix: 4×4 Signal grade (A/B/C/F) vs Trade grade (A/B/C/F)
Cells map to MAJOR, STRONG, SOLID, WEAK, and SKIP tier labels; dashed cells show where B-signal returns SOLID before trade grade is checked

Verdicts that explain themselves

A verdict engine weighs 7-day, 30-day, and 90-day percentile position into plain-English calls, from SELL NOW to DON’T SELL, each with its reasoning attached. Nothing appears on screen without a letter-grade and a reason why. Dump detection doesn’t just classify the drop, it also measures how long recovery historically takes.

GE Terminal Item Lookup for grimy toadflax: SELL NOW verdict
Price at multi-timeframe highs — 7-day 8th percentile, 30-day 36th percentile, 90-day 40th percentile
Python source: is_dumping() function body
Two magic numbers carry the entire heuristic: an 8% hourly drop and 1.5× typical volume. Every dump the tool has ever reported traces back to this line.
The algorithm serves the interface, and the interface serves a decision a human has to make under uncertainty.

In Use

Eighteen-plus versions, running daily, still the tool I trade with.

Early versions had no exit logic at all. I’d buy into something that looked like it was climbing and then sit on it, holding inventory I couldn’t read: no sell zone, no sense of whether it was coming back or whether I should take the loss. That’s where the verdict engine came from, and the recovery window with it. The tool learned to tell me when to get out because I’d already proved I couldn’t work it out myself.

GE Terminal watchlist card for Grimy toadflax
DUMP zone badge (top 20% of 7-day band), declining price sparkline, current 2,011 gp versus sell zone 1,966 gp, Trade A grade, max position 22k at 15% allocation

Learning

The thing I’d fix first isn’t a feature. I let six classification systems accumulate in parallel, each with its own vocabulary. Individually every one is defensible; they were added months apart, each solving a real problem.

None of this was a decision so much as an accumulation. Each system arrived with a feature, named itself sensibly in isolation, and never got reconciled against the ones already there. The cost isn’t confusion inside any single card; every card is readable. It’s that fluency doesn’t transfer. Learning what ACCUMULATE means teaches you nothing about what HOLD or WAIT means, even though they’re the same measurement over a different window.

The fix isn’t fewer states, it’s fewer vocabularies. Zone and Verdict collapse into one percentile scale with a timeframe parameter. Signal grade and trade grade stop surfacing as letters at all and exist only as inputs to tier. Six scales down to three, and each remaining answering something the others can’t: where the price sits, whether the item is worth trading, and whether to act now.

GE Terminal classification vocabulary
Signal grade (A–F, timing), Trade grade (A–F, safety), Tier (MAJOR/STRONG/SOLID/WEAK/SKIP), Zone state (BROKEN through DUMP), Verdict (SELL NOW through DON’T SELL), and Pattern (PERIODIC through NONE)

No single naming decision was wrong because each one was sensible when it was made. However, across eighteen versions I never asked whether they still added up to one language.

Letting a system refuse to act, and show why it refused, is the thing I keep taking into design work with AI.

Origin

I started this with zero Python background. GE Terminal became a way to learn by building something I’d actually use. 18+ versions later, it isn’t shipped software, but it’s built like it wants to be: cached API layers, state persistence, alert thresholds a user can trust.

It’s what happens when the barrier between an idea and a working tool gets low enough that a designer stops waiting for an engineer.