Skip to content

252 strategies, and the bugs they didn’t catch

8 min read

The parity corpus grew from 167 to 252 reference strategies by the end of June. Three bugs it never exercised showed up in community-shared scripts: a margin-call liquidation gap, an FX-blind affordability check, and a trailing stop that silently never armed.

The figures in this post were measured on June 29, 2026 and are kept as they were. For current results, see how we measure parity.

When we last wrote up the corpus in May, the parity corpus held 167 reference strategies, 165 of them at strict trade-for-trade parity against TradingView. It grew after that: by the end of June it held 252 strategies, 251 at "excellent" parity (99.6 %), one documented anomaly, zero failures.

The headline number is the boring part. The interesting part is what the corpus didn't exercise: three TradingView broker behaviors we weren't modeling, each one found because a community-shared script hit a code path none of the 252 did. This is a walk-through of three of them.

Where the corpus stood on 2026-06-29

corpus/validation_report.md (2026-06-29)
  Total probes:    252
  Excellent:       251  (99.6%)
  Anomaly:           1
  Strong/Moderate/Weak/Minimal/Missing: 0
 
  TV trades:      389,590
  Engine trades:  389,688
  Matched:        389,468

The engine emitted 98 more trades than TradingView across the full corpus, a 0.025 % delta on 389,590 reference trades. That was the aggregate; per strategy, the one anomaly below accounted for a single trade of it. The rest came from 23 strategies still graded excellent under that report's rubric, which allowed a trade-count gap under 1 %: most were off by one or two trades, and four made up 90 of the 98.

The newest six probes at the time were a drawing category, added when we shipped a runtime for reading Pine drawing objects (line.get_price, label/box geometry) as backtest-time data, not just chart annotations. Strategies that use a drawn line as computational state (a manually-anchored trendline used as a dynamic support level, say) need the engine to keep serving geometry for objects the script has already deleted, the same way TradingView does. All six graded excellent.

The one anomaly, named honestly

We didn't filter the one strategy that didn't match. anomaly-equity-mirror-strategy-equity-01 sits at the exact 1× equity boundary for margin admission. Our analysis at the time, which the corpus notes rate at 95 % confidence or more, was that TradingView's broker emulator was non-monotonic right at that boundary: it admitted some over-equity entries and rejected some under-equity ones, inconsistently, bar to bar. Our engine is deterministic: same equity, same admission decision, every time. That's correct per Pine's documented margin semantics, and by that analysis it was not what TradingView's broker did at that one boundary. Matching it would have meant adding our own non-determinism, so we didn't. The gap was documented, not hidden, and it was the only one of 252. (Since then the maintainers' 2026-09-29 baseline has graded this probe excellent, and the corpus README marks that diagnosis as under review.)

Bug one: the position TV would have force-closed

TradingView's broker doesn't wait for your strategy logic to decide when to exit a position that's blown through its margin requirement. It liquidates. If a leveraged or short position's equity drops far enough that required margin exceeds account equity, TradingView liquidates on the bar where it happens, at that bar's worst price for the position, closing four times the amount needed to restore the margin (at most the whole position), no matter what strategy.exit() conditions are or aren't met.

The engine didn't model this. It would hold the position and let the strategy's own logic eventually close it, which on a strategy that doesn't have a tight stop could mean dozens of bars of a position TradingView had already liquidated. On 24 community-shared scripts that rode leveraged or short positions into a margin call, TradingView's trade list had "Margin call" exits the engine never produced.

The fix adds forced liquidation when account equity breaches the margin requirement, computing the liquidation price the same way TradingView's broker does, then quantizing the liquidated quantity to the symbol's lot step. TradingView doesn't liquidate fractional lots smaller than the symbol's lot step, and neither do we now, when the run supplies that step.

This matters most for anyone backtesting leveraged or short strategies on perpetuals. If your equity curve looked too smooth during a drawdown, this was plausibly why.

Bug two: the affordability check that didn't know about FX

Pine strategies can declare an account currency that differs from the symbol's quote currency: currency.INR trading a USDT pair, for example. Before checking whether you can afford an order, TradingView's broker converts the order's notional value into your account currency using the prevailing FX rate, then compares against equity in that same currency.

The engine's affordability gate skipped the conversion. It compared USDT notional directly against INR-denominated equity as if the FX rate were 1:1, meaning a strategy declaring currency.INR looked dozens of times more affordable than it should have (one USDT is worth dozens of rupees). On affected scripts, the engine fired roughly twice as many trades as TradingView, admitting entries TradingView's broker rejected as unaffordable.

The fix applies the account-currency FX rate to required margin before the affordability check runs, the same point in the order pipeline TradingView applies it, at a rate the run supplies (the engine's default is 1:1). If you backtested with a non-USD account currency against a USD- or USDT-quoted symbol, this was the bug quietly inflating your trade count.

Bug three: the trailing stop that never armed

This is the one we'd most want a user to have caught before us, because it fails silently. strategy.exit() supports two ways to specify a trailing stop: trail_points, an entry-relative distance, and trail_price, an absolute activation price.

// armed correctly today; trail_price-only calls used to never arm at all
strategy.exit("TS", from_entry = "Long", trail_price = close * 1.05, trail_offset = 10)

The engine's arming condition only checked trail_points. A trailing exit specified purely via trail_price compiled without error, ran without error, and never armed. The position just sat there with no trailing stop active, silently, for the entire backtest. No exception, no log line, no trade. The strategy looked protected. It wasn't.

We found this because a community-shared script that sets its trailing stops with trail_price alone matched only half of TradingView's trades; with the fix it matched 99.7 %. No probe in the open corpus used trail_price, so the corpus numbers could not show the gap. That is the kind of gap the validation category exists for, per the corpus breakdown post: narrow, synthetic strategies, not profitable, not realistic, built to exercise one Pine feature in isolation so a regression like this can't hide behind "the overall numbers looked fine."

The fix arms the trailing stop if either trail_points or trail_price is present. If you've ever written a Pine strategy using trail_price and the equity curve looked suspiciously like there was no trailing stop at all, there wasn't, on any engine release before v0.10.14 (2026-07-02), the first to ship the fix.

Why this is the actual point of a corpus

None of these three bugs were found by code review. They were found because a strategy with the right shape ran through the engine and produced a number that didn't match TradingView's. That's the whole thesis behind keeping 167 strategies, then 252, and counting as a held-out, versioned corpus instead of a handful of smoke tests, and behind running community-shared scripts through the same diff, which is where these three turned up: a clean headline number can hide a bug that changes what a strategy made or lost, and the only way to catch those is to keep running more shapes of strategy through the same diff against the same ground truth.

We'll keep adding probes as we find gaps, and keep publishing the numbers (good and the one anomaly) as they stand.

Where to go from here