How the Top-Up Score Works

A complete breakdown of the mathematics behind our fuel fill-up recommendation. From raw price data to the final score on the gauge.

The Top-Up Score

The Top-Up Score is a single number from 0 to 100 that tells you how urgently you should fill up. It combines price momentum, supply risk, market signals, and cycle timing into one actionable metric.

5

Don't bother

25

No rush

50

Neutral

75

Fill up soon

95

Fill everything

The Master Formula

The Top-Up Score is a weighted sum of four independent sub-scores, each normalised to 0-100. A crisis override can push the score to maximum regardless of the formula.

STopUp=clamp ⁣(0,  100,  iwiSi+Scrisis)S_{\text{TopUp}} = \text{clamp}\!\left(0,\;100,\;\sum_{i} w_i \cdot S_i + S_{\text{crisis}}\right)

Expanding with the component weights:

STopUp=0.40Smomentum+0.10Scycle+0.25Smacro+0.25SsupplyS_{\text{TopUp}} = 0.40\,S_{\text{momentum}} + 0.10\,S_{\text{cycle}} + 0.25\,S_{\text{macro}} + 0.25\,S_{\text{supply}}
ComponentWeightWhat it captures
SmomentumS_{\text{momentum}}w1=0.40w_1 = 0.40Are prices going up or down? How fast? How confident?
ScycleS_{\text{cycle}}w2=0.10w_2 = 0.10Where are we in the weekly price cycle?
SmacroS_{\text{macro}}w3=0.25w_3 = 0.25Oil markets, FX rates, and prediction market odds
SsupplyS_{\text{supply}}w4=0.25w_4 = 0.25Are stations running dry? Is it accelerating?

1. Price Momentum Score

Weight: 40% of final score

The model predicts how much fuel prices will change over the next 3 and 7 days. We blend these into a single directional signal:

d=0.6Δ3+0.4Δ7d = 0.6 \cdot \Delta_3 + 0.4 \cdot \Delta_7

where Δ3\Delta_3 and Δ7\Delta_7are the predicted price changes in c/L. This is then normalised to a 0-100 scale over a range of ±40c/L:

Sraw=d+4080×100S_{\text{raw}} = \frac{d + 40}{80} \times 100

A confidence adjustment handles uncertainty. The model produces a confidence interval [CI10,CI90][\text{CI}_{10},\, \text{CI}_{90}]. When this interval is wide, the model is less sure. If it's uncertain and suggesting to wait, we nudge upward (safer to fill up):

wCI=CI90CI10w_{\text{CI}} = \text{CI}_{90} - \text{CI}_{10}
penalty=max ⁣(0,  wCI1530)×10\text{penalty} = \max\!\left(0,\; \frac{w_{\text{CI}} - 15}{30}\right) \times 10
Smomentum={Sraw+penaltyif Sraw<50SrawotherwiseS_{\text{momentum}} = \begin{cases} S_{\text{raw}} + \text{penalty} & \text{if } S_{\text{raw}} < 50 \\ S_{\text{raw}} & \text{otherwise} \end{cases}
Why the asymmetric nudge?Filling up when you didn't need to costs a few dollars. Waiting and getting caught by a price spike (or empty stations) costs much more. We bias toward the safer action when the model is unsure.

How the model produces Δ3\Delta_3 and Δ7\Delta_7

These predictions come from a LightGBM gradient-boosted tree model. Three separate models are trained (quantile regression at the 10th, 50th, and 90th percentiles) to produce the median forecast and the confidence interval.

Training data. The model is trained on Queensland Government Open Data(data.qld.gov.au), which provides station-level fuel price transactions from December 2018 to present. This is the most granular and longest-running open dataset available from any Australian state. The same QLD-trained model is applied as a directional proxy for all cities — it captures general Australian fuel price cycle patterns but may not reflect state-specific dynamics. As we integrate historical data from other states (NSW FuelCheck, WA FuelWatch), per-state models will follow.

For a given city cc and forecast horizon h{3,7}h \in \{3, 7\}, the model minimises the quantile loss:

Lτ(y,y^)=1Ni=1N{τ(yiy^i)yiy^i(1τ)(y^iyi)yi<y^i\mathcal{L}_\tau(y, \hat{y}) = \frac{1}{N} \sum_{i=1}^{N} \begin{cases} \tau \, (y_i - \hat{y}_i) & y_i \geq \hat{y}_i \\ (1 - \tau)(\hat{y}_i - y_i) & y_i < \hat{y}_i \end{cases}

where τ{0.1,0.5,0.9}\tau \in \{0.1, 0.5, 0.9\} gives the 10th, 50th (median), and 90th percentile models respectively. The final prediction and confidence interval are:

Δh(c)=f^τ=0.5(xt(c)),CI=[f^τ=0.1(xt(c)),  f^τ=0.9(xt(c))]\Delta_h^{(c)} = \hat{f}_{\tau=0.5}(\mathbf{x}^{(c)}_t), \qquad \text{CI} = \Big[\hat{f}_{\tau=0.1}(\mathbf{x}^{(c)}_t),\; \hat{f}_{\tau=0.9}(\mathbf{x}^{(c)}_t)\Big]

where xt\mathbf{x}_t is the feature vector at time tt, comprising 24 features across price momentum, volatility, cycle position, and calendar signals.

The 24 features that feed the model:

delta_1d

price change, last 24h

delta_3d

price change, last 3 days

delta_7d

price change, last 7 days

delta_14d

price change, last 14 days

price_vs_7d_mean

z-score vs 7-day avg

price_vs_30d_mean

z-score vs 30-day avg

price_vs_90d_mean

z-score vs 90-day avg

volatility_7d

7-day rolling std dev

volatility_14d

14-day rolling std dev

volatility_30d

30-day rolling std dev

price_acceleration

2nd derivative of price

intraday_range

max-min across stations

percentile_rank

position in 90-day range

dow

day of week (0-6)

is_weekend

Saturday/Sunday flag

month

month of year (1-12)

cycle_position

deviation from 7d MA

momentum_3d

normalised 3d momentum

momentum_7d

normalised 7d momentum

roc_7d

7-day rate of change %

roc_14d

14-day rate of change %

price_median

current daily median

ma_7d

7-day moving average

ma_30d

30-day moving average

Note: These 24 features drive the LightGBM price forecast. Macro signals (Brent, AUD/USD, gasoline, Polymarket, spread) and supply metrics (outage rates) are incorporated separately via the Top-Up Score formula, which blends the model forecast with live market conditions to produce the final recommendation.

The model is retrained manually on the full historical dataset. On held-out test data (last 90 days): 3-day MAE ~5.2c/L, directional accuracy ~74%. 7-day MAE ~10.1c/L, directional accuracy ~77%.

2. Cycle Timing Score

Weight: 10% of final score

Australian fuel prices follow weekly cycles (well-documented by the ACCC). Prices bottom out mid-week and peak on weekends. We track the cycle phase ϕ[0,1]\phi \in [0, 1]: 0 = just hit the trough, 1 = approaching next trough.

Scycle={ϕ0.3×30ϕ<0.3(near trough, prices still low)30+ϕ0.30.4×600.3ϕ<0.7(rising, fill now)90ϕ0.70.3×20ϕ0.7(near peak, will drop soon)S_{\text{cycle}} = \begin{cases} \dfrac{\phi}{0.3} \times 30 & \phi < 0.3 \quad \text{(near trough, prices still low)} \\[8pt] 30 + \dfrac{\phi - 0.3}{0.4} \times 60 & 0.3 \leq \phi < 0.7 \quad \text{(rising, fill now)} \\[8pt] 90 - \dfrac{\phi - 0.7}{0.3} \times 20 & \phi \geq 0.7 \quad \text{(near peak, will drop soon)} \end{cases}

An additional bonus applies if the current cycle has stretched beyond the rolling average length Lˉ\bar{L}:

if   tsince trough>1.2Lˉ:Scycle+=10\text{if } \; t_{\text{since trough}} > 1.2 \cdot \bar{L}: \quad S_{\text{cycle}} \mathrel{+}= 10
Why only 10%? The weekly cycle is predictable but its magnitude is small (15-30c swing) compared to macro shocks. During the current crisis, cycle timing is nearly irrelevant because crude oil movements dwarf the weekly pattern.

3. Macro Signal Score

Weight: 25% of final score

Global oil prices, the AUD/USD exchange rate, and prediction markets signal where Australian fuel costs are heading. Each sub-signal is normalised to 0-100:

Sbrent=clamp ⁣(0,  100,  PBrent6060×100)S_{\text{brent}} = \text{clamp}\!\left(0,\; 100,\; \frac{P_{\text{Brent}} - 60}{60} \times 100\right)

Brent crude oil price level. Maps the current Brent price to urgency: $60/bbl = 0 (no pressure), $90/bbl = 50 (moderate), $120/bbl = 100 (maximum pressure). Fetched live from Yahoo Finance.

SAUD=clamp ⁣(0,  100,  0.78RAUD/USD0.16×100)S_{\text{AUD}} = \text{clamp}\!\left(0,\; 100,\; \frac{0.78 - R_{\text{AUD/USD}}}{0.16} \times 100\right)

AUD/USD exchange rate. Australia imports fuel priced in US dollars. A weaker AUD means more expensive fuel. At 0.78 = no pressure (score 0), at 0.62 = maximum pressure (score 100). Fetched live from the Frankfurter FX API.

SΔbrent=clamp ⁣(0,  100,  ΔBrent+510×100)S_{\Delta\text{brent}} = \text{clamp}\!\left(0,\; 100,\; \frac{\Delta_{\text{Brent}} + 5}{10} \times 100\right)

Brent daily change. Captures short-term momentum in oil markets. A $5/day drop = 0 (easing), a $5/day rise = 100 (spiking). This reacts faster than the price level signal.

Sgasoline=clamp ⁣(0,  100,  PRBOB1.502.50×100)S_{\text{gasoline}} = \text{clamp}\!\left(0,\; 100,\; \frac{P_{\text{RBOB}} - 1.50}{2.50} \times 100\right)

RBOB Gasoline futures(proxy for Singapore MOGAS 95). This is the refined product benchmark — more predictive than crude alone because it captures refining margin pressure. $1.50/gal = 0, $2.75 = 50, $4.00 = 100. Fetched live from Yahoo Finance (RB=F).

Spoly=P(crude$100)S_{\text{poly}} = P(\text{crude} \geq \$100)

Polymarket prediction odds.Real-time odds from Polymarket's crude oil prediction markets via the Gamma API. P(crude ≥ $100) maps directly to 0–100. Thousands of traders betting real money make this a powerful crowdsourced leading indicator.

Sspread=clamp ⁣(0,  100,  spread520×100)S_{\text{spread}} = \text{clamp}\!\left(0,\; 100,\; \frac{\text{spread} - 5}{20} \times 100\right)

Wholesale-retail spread.Approximated as the gap between the 5th percentile of station prices (proxy for terminal gate) and the national median. A tight spread (~5c/L) means competitive pricing; a wide spread (~25c/L) means retailers are extracting premium — prices may be near a cycle peak. Computed live from CheckPetrol station data.

These six sub-signals are combined as:

Smacro=0.25Sbrent+0.15SAUD+0.15SΔbrent+0.15Sgasoline+0.15Spoly+0.15SspreadS_{\text{macro}} = 0.25\,S_{\text{brent}} + 0.15\,S_{\text{AUD}} + 0.15\,S_{\Delta\text{brent}} + 0.15\,S_{\text{gasoline}} + 0.15\,S_{\text{poly}} + 0.15\,S_{\text{spread}}

Sources: Brent, WTI, gasoline via Yahoo Finance. AUD/USD via Frankfurter. Polymarket via Gamma API. Wholesale-retail spread computed from live station prices.

4. Supply Risk Score

Weight: 25% of final score

The crisis detector. During normal times it sits near zero. During supply disruptions, it dominates. Uses an exponential curve so low outage rates barely register but high rates ramp up fast.

Ssupply=100×(1e5routage)S_{\text{supply}} = 100 \times \left(1 - e^{-5 \, r_{\text{outage}}}\right)

where routager_{\text{outage}} is the fraction of stations currently reporting as dry (detected from the has_outage flag in the CheckPetrol API).

How the curve behaves:

1% outage

~5

5% outage

~22

10% outage

~39

20% outage

~63

The exponential shape means 1-3% outages (normal churn) barely move the score, but once outages cross ~10% the score accelerates sharply — reflecting the non-linear nature of supply crises where shortages compound.

Crisis override: If routage>15%r_{\text{outage}} > 15\%, the entire Top-Up Score is forced to 100 regardless of other components. At that point, availability is the concern, not price.

Worked Examples

Normal Tuesday in Melbourne (Score: 22)

Price cycle just hit its weekly trough. Crude oil flat. No outages.

Smomentum=38S_{\text{momentum}} = 38 Δ3=8c,  Δ7=12c\Delta_3 = -8\text{c}, \; \Delta_7 = -12\text{c}

Scycle=5S_{\text{cycle}} = 5 ϕ=0.05\phi = 0.05 (just hit trough)

Smacro=48S_{\text{macro}} = 48 Brent flat, AUD stable

Ssupply=0S_{\text{supply}} = 0 0% outages

S=0.40(38)+0.10(5)+0.25(48)+0.25(0)=15.2+0.5+12.0+0=27.7S = 0.40(38) + 0.10(5) + 0.25(48) + 0.25(0) = 15.2 + 0.5 + 12.0 + 0 = \mathbf{27.7}

Thursday in Sydney, prices climbing (Score: 66)

Mid-cycle, prices rising. Brent crude jumped $5 this week. AUD weakened.

Smomentum=71S_{\text{momentum}} = 71 Δ3=+14c,  Δ7=+20c\Delta_3 = +14\text{c}, \; \Delta_7 = +20\text{c}

Scycle=68S_{\text{cycle}} = 68 ϕ=0.55\phi = 0.55 (mid-rise)

Smacro=72S_{\text{macro}} = 72 Brent +$5, AUD -1.5c

Ssupply=10S_{\text{supply}} = 10 2% outages

S=0.40(71)+0.10(68)+0.25(72)+0.25(10)=28.4+6.8+18.0+2.5=55.7S = 0.40(71) + 0.10(68) + 0.25(72) + 0.25(10) = 28.4 + 6.8 + 18.0 + 2.5 = \mathbf{55.7}

March 2026 crisis (Score: 100, override)

Strait of Hormuz closed. 18% of Sydney stations dry. Outages accelerating.

routage=18%>15%r_{\text{outage}} = 18\% > 15\% → CRISIS OVERRIDE

voutage=8>5v_{\text{outage}} = 8 > 5 → CRISIS OVERRIDE

STopUp=100S_{\text{TopUp}} = 100 Fill everything. Now.

(Formula alone would have produced 89)

Savings Calculation

Every recommendation includes a dollar-amount savings estimate, the number people screenshot and send to group chats.

savings=Δ3×Vtank100\text{savings} = \frac{|\Delta_3| \times V_{\text{tank}}}{100}

Where Vtank=50LV_{\text{tank}} = 50\text{L} (typical Australian passenger car). For example, a predicted rise of Δ3=+18c/L\Delta_3 = +18\text{c/L} gives:

savings=18×50100=$9.00\text{savings} = \frac{18 \times 50}{100} = \$9.00

The Prediction Model

The heart of the system is a LightGBMgradient-boosted tree model trained on 7+ years of Queensland Government fuel price data (Dec 2018 – present, via data.qld.gov.au). The QLD model is applied as a directional proxy for all Australian cities.

Why LightGBM?

  • GBMs match or beat deep learning on tabular/structured data
  • Trains in seconds, not hours
  • Native feature importance lets us explain why it predicts what it does
  • Handles missing data gracefully
  • Inference in <1ms, no GPU required

What it predicts

  • Δ3\Delta_3 price change, next 3 days
  • Δ7\Delta_7 price change, next 7 days
  • CI10\text{CI}_{10} 10th percentile (worst case)
  • CI50\text{CI}_{50} median forecast
  • CI90\text{CI}_{90} 90th percentile (best case)
  • Single QLD aggregate model, applied as proxy for all cities

Feature Set (24 features per day)

Price Momentum (7)

delta 1d/3d/7d/14d, z-scores vs 7d/30d/90d MA

Volatility (6)

7d/14d/30d std dev, acceleration, intraday range, percentile rank

Cycle & Momentum (5)

cycle position, 3d/7d momentum, 7d/14d rate of change

Calendar & Level (6)

day of week, weekend flag, month, median price, 7d/30d MA

Data Sources

PRICES

CheckPetrol API (aggregates all state feeds)

6,500+ stations across all states. Prices cached and refreshed every 15 min.

MACRO

Brent Crude, WTI, AUD/USD, Gasoline Futures

Yahoo Finance (no API key), Frankfurter FX API. Cached hourly.

MARKETS

Polymarket Prediction Markets

Gamma API. Crude oil price prediction odds, real-time. No API key required.

SUPPLY

Outage Detection

Inferred from has_outage flags in CheckPetrol API. Updated with each price refresh.

Accuracy & Retraining

5.2c/L

MAE, 3-day forecast

74%

Directional Accuracy, 3-day

77%

Directional Accuracy, 7-day