Engineering

252 strategies, and the bugs they caught

The parity corpus grew from 167 to 252 reference strategies. Here's what the growth caught: a margin-call liquidation gap, an FX-blind affordability check, and a trailing stop that silently never armed.
8 min read#parity#corpus#engine#tradingview#broker-emulation

When we last published corpus numbers in May, the parity corpus held 167 reference strategies, 165 of them at strict trade-for-trade parity against TradingView. It's grown since. The corpus now holds 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 showed up while we were getting there: three TradingView broker behaviors we weren't modeling, each one found because a real or synthetic strategy exercised a code path the existing 167 didn't. This is a walk-through of three of them.

Where the corpus stands

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

The engine emits 98 more trades than TradingView across the full corpus — a 0.025% delta on 389,590 reference trades. That's the aggregate; per-strategy, almost all of it is the one anomaly below, not noise spread across the other 251.

The newest six probes are 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 passed clean.

The one anomaly, named honestly

We don't filter the one strategy that doesn't match. anomaly-equity-mirror-strategy-equity-01 sits at the exact 1× equity boundary for margin admission. TradingView's broker emulator is non-monotonic right at that boundary — it admits some over-equity entries and rejects 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. It is also, provably, not what TradingView's broker actually does at that one boundary. We'd have to introduce our own non-determinism to match it, so we don't. The gap is documented, not hidden, and it's the only one of 252.

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 force-closes the position at the next bar, 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 corpus strategies with significant leverage, that produced wildly different equity curves under stress, even when entry and exit logic matched perfectly everywhere else.

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 exchange allows, and neither do we now.

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 roughly 83× more affordable than it should have (since 1 USDT is worth roughly that many rupees). On affected scripts, that roughly doubled the number of trades the engine admitted that TradingView's broker would have 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. If you're backtesting with a non-USD-denominated account currency against a USD- or USDT-quoted symbol, this is the bug that was 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 corpus probe specifically isolates trail_price-only activation — exactly the kind of narrow, synthetic strategy the validation category exists for, per the corpus breakdown post: 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 version before this one.

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: bugs that don't change the trade count or the entry price can still change the number that matters most — what the 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