§ methodology · v1

How wavealgo measures.

A script leaves this pipeline with two things: an alpha score from 0 to 100, and one of four verdicts. Both are arithmetic on measured numbers — no judgement call, no curve, no per-script tuning. This page documents every assumption behind them so you can reproduce or challenge any number on the site.

§ 01

The 18-cell matrix

Every script is backtested across a fixed grid of 6 markets × 3 timeframes. The grid is fixed by design — same rules, every script, every run — so two strategies tested on different days can still be compared on the same yardstick.

MarketsSourceTimeframes
BTCUSD (BTCUSDT)Binance public klines1H
ETHUSD (ETHUSDT)Binance public klines4H
SPX500 (SPY proxy)Yahoo Finance1D
QQQYahoo Finance
GOLD (GLD proxy)Yahoo Finance
EURUSDYahoo Finance

Each cell takes the deepest history its source will serve, with daily capped at 4,000 bars so every daily cell spans the same recent window rather than one starting in 1993 and another in 2004. In the run published here (2026-08-08) that came to 1D = 3,278–4,000 bars, 4H = 1,707–19,653, 1H = 5,090–78,545. The spread is the source, not a setting: Binance serves crypto intraday back to 2017, while Yahoo caps hourly history at 730 days. Cells are therefore not equal-length, and each one records the bar count it actually got — which is why the composite is a median rather than a mean (§02).

§ 02

The alpha score

Each cell produces its own numbers — Sharpe, return, max drawdown, win rate, profit factor, trade count, and return relative to buy-and-hold. Those 18 cells collapse into a composite Sharpe (the median of the 18 cell Sharpes, not the mean — one long cell should not be able to carry the other seventeen), and the alpha score is four components added together.

ComponentRangeHow it is computed
Out-of-sample edge0 to +40clamp(composite Sharpe / 2, 0, 1) × 40
Regime stability0 to +25positive cells / 18 × 25
Flaw penalty−25 to 0static repaint / look-ahead findings
Reproduction accuracy0 to +10engine oracle parity vs the Python reference — 251/257 scripts = 9

Printed verbatim from the run that produced every number on this site: alpha = edge (0-40: clamp(compositeSharpe/2,0,1)*40) + stability (0-25: positiveCells/18*25) + flaw penalty (0 to -25: static findings, 0 across v1 catalog) + reproduction (0-10: engine oracle parity vs pine2py, 251/257 scripts = 9)

Two components are currently constant across the published catalog: reproduction is 9 for every script (it grades the engine, not the strategy) and the flaw penalty is 0 (see §06 for why that is a weaker statement than it looks). So the spread you see on the leaderboard comes almost entirely from edge and stability. A score is a measurement, not a curve — nothing is normalized against the other entries, and no letter grade appears in anything published here.

§ 03

The four verdicts

The score says how much; the verdict says what kind. It is a separate decision from a separate rule — a script can carry a respectable alpha score and still be Conditional because its edge lives in one cell out of eighteen.

VerdictRule
Verifiedcomposite Sharpe ≥ 0.9 and at least 12 of 18 cells positive
Conditionalcomposite Sharpe > 0 and best cell Sharpe ≥ 0.9
Repaintsa static repaint / look-ahead finding in the source
No alphaanything else

Printed verbatim from the same run: pass: composite Sharpe >= 0.9 and >= 12/18 cells positive · cond: composite Sharpe > 0 and best cell Sharpe >= 0.9 · fail: otherwise · rep: static repaint/look-ahead finding (none in the authored v1 catalog: confirmed-bar signals, next-bar-open fills)

What the rules produced. Across the 20 scripts audited for v1: 0 Verified, 16 Conditional, 0 Repaints, 4 No alpha. Alpha scores run 2349 out of 100, and 17 of the 20 trail simply holding the asset. These are the best-known public techniques, run through the rules above. A much wider survey — 12,424 public scripts — is published separately on the corpus sweep.

§ 04

Backtest assumptions

We try to be honest about what we model and what we don't. None of the numbers below are personalized to a particular broker or venue — they're a single shared yardstick.

AssumptionValue
Initial capital100,000
Position size100% of equity per entry (long-only)
Commission model0.05% per side (percent)
Slippage0 (not modeled in v1)
Sharpe annualizationdaily UTC equity returns × √252
Bar fillMarket orders at the next bar open (TradingView rule)
Risk-free rate0

Fee: 0.05% per side is the midpoint between Binance spot (0.10%) and a cheap stock broker (0.005%). High-frequency strategies will feel this; daily strategies barely notice. Slippage: not modeled in v1, so signals that depend on a clean fill (especially low-liquidity assets, fast breakouts) will look better here than they will in practice. We will surface a slippage assumption in v2. Fills: a market order placed on bar n fills at the open of bar n+1, which is TradingView's documented behaviour and a deliberate departure from the Python engine this one replaced (that one filled at the same bar's close, which we consider a bug).

§ 05

What gets scored

wavealgo only scores complete trading systems — strategy(...) declarations with strategy.entry / strategy.close calls. Indicators alone (an indicator(...) that just plots) are rejected up front because there is no entry/exit rule to measure.

Every one of the 20 scripts in the v1 catalog is a strategy, and every one of them is our own reference implementation of a well-known public technique — not a third-party submission. That matters when you read the flaw penalty in §02: they are clean because we wrote them to be, with signals bound to confirmed-bar values and orders filling at the next bar open. strategy("...") with bound signals (sigLong = ta.crossover(...)) is the only shape this scorer accepts. An indicator that only plots can still be measured — the corpus sweep trades signal indicators through a published entry/exit harness and scores continuous ones on predictive power, under its own rulebook rather than this one.

§ 06

Known limitations

Things v1 does not handle well. Listed publicly so you know what to discount.

  • Inline cross calls inside if: if ta.crossover(rsi, 30) evaluates against scalar bar values and degenerates to constant-false. Bind to a variable: sigLong = ta.crossover(rsi, 30) then if sigLong.
  • Sub-hourly history is short for non-crypto: 15m / 30m bars only go back ~60 days for SPY / QQQ / GLD / EURUSD (Yahoo Finance limit). v1 only scores 1H / 4H / 1D so this doesn't affect the alpha score, but a future 30m cell will be skipped on those four assets.
  • A zero flaw penalty is an absence of findings, not a clean bill: the Repaints verdict and the flaw penalty are driven by static checks on the source. Across the v1 catalog both come out at zero, which is unsurprising — those scripts are authored in-house and have nothing to repaint by construction (§05). The stronger, dynamic check is a truncation-invariance test: the script is executed twice, once with the last 200 bars removed, and the equity curves are compared over the bars they share, because a causal strategy cannot notice that the future was deleted. That check runs on the corpus sweep and is not wired into this alpha path yet. Read a zero penalty here as "nothing was found by the static checks".
  • Fills follow TradingView's rule, but that has not been confirmed against TradingView: Market orders fill at the next bar's open, which is TradingView's documented behaviour. The rule is implemented from the specification; a reference strategy has not yet been run on TradingView itself to confirm the two agree trade for trade. Fills are not reconciled trade-by-trade against TradingView — treat absolute returns as provisional until they are.
  • Intrabar fills cannot be confirmed from bars: A stop, a limit or a trailing exit fills somewhere inside a bar, and OHLC data does not record where. Any script using one has a backtest that partly reflects an assumed path through the bar rather than measured behaviour. The corpus sweep labels these; this scorer does not.
  • Cells are not equal-length: a 1H cell on BTCUSD covers years of Binance history while a 1H cell on GOLD is capped at 730 days by Yahoo (§01). The median composite keeps a single long cell from dominating, but a per-cell Sharpe is still a statement about a different window for each market.
  • Single asset universe: six markets is enough for first-order judgment but under-represents commodities, frontier markets, and small-cap equities. Universe expansion is on the v2 plan.

§ 07

Versioning

Methodology changes get a version bump. Past scores keep a label of the version they were computed under. The /methodology page always reflects the current version.

VersionActive sinceChange
v12026-04-29Initial public methodology

The numbers currently published come from the run stamped 2026-08-08T04:53:53.203Z on engine pine2js (next-bar-open fills, TV rule). Those two strings travel with the data, so a figure on the leaderboard can always be traced back to the exact run that produced it.

§ 08

Reproducing a score

There is no scoring server. Nothing about a score depends on a service being up, on who you are, or on when you ask — the two code paths below are the whole of it, and both are deterministic.

PathWhere it runsWhat it produces
Playgroundyour browser, on resina chart and a run of your own script — the Pine never leaves the browser
Audit pipelineoffline, tools/js_score/score.mjsthe 18-cell run behind the leaderboard, written to src/lib/desk-live.json

The compiler and runtime are open source: nullarch/resin. The alpha formula and the verdict thresholds are the handful of lines quoted verbatim in §02 and §03, and the run they came from is the one stamped in §07. The Python implementation the engine is checked against (251/257 scripts, bar-by-bar, 1e-9 tolerance) is kept as an offline reference oracle — it does not serve traffic and it does not score anything you submit. There is no hidden model, no stochastic component, and no per-user tuning.

Disagree with a number? Open a discussion on the public wavealgo repo — our reply will reference the exact line.