PineTunnel

94
█ OVERVIEW

PineTunnel is a Pine Script library that re-implements TradingView's
strategy.* broker emulator for indicator() scripts. It mirrors the full
strategy.* namespace one-to-one — 114 exports: 88 functions, 18
constants and 8 types — covering entries, brackets, trailing stops,
OCA groups, pyramiding, reversals, margin calls, the five risk guards
and intrabar order processing. Every engine behavior was validated
against the Strategy Tester itself, with a dual-engine probe suite
comparing closed trades row by row.

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ HOW IT WORKS

Pine Script®


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ WHY THIS LIBRARY MATTERS

▪ PORTFOLIO BACKTESTING — one engine per symbol, bars delivered via
request.security(), equity and trades aggregated into a single
portfolio view

▪ HEDGED BOOKS — long and short engines running side by side, on one
symbol or across the whole portfolio

▪ MULTI-TIMEFRAME BACKTESTING — onBar() accepts any OHLC series:
feed higher-timeframe composites, custom sessions, or run one
engine per timeframe inside a single script

▪ SIDE-BY-SIDE VARIANTS — several configurations on one chart, each
calculating its own settings

▪ ONE CODEBASE, BACKTEST TO LIVE — the same indicator that draws
your plots and fires your alerts produces the backtest

▪ TOTAL TRANSPARENCY — every fill, leg, bracket and statistic is a
value you can plot, log, or route to alerts and webhooks

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ ORIGINALITY AND USEFULNESS

This library is original work that adds value to the TradingView
community in the following ways:

1. Full strategy.* Namespace Mirror: Implements all 114 members of
the official strategy.* namespace as library exports with identical
leaf names and semantics (dots → underscores). makePTConfig()
accepts the same properties as the strategy() declaration, with
the same defaults.

2. Broker-Emulator Execution: Implements market, limit, stop and
stop-limit entries; TP/SL/trailing brackets with per-fill trail
state; OCA groups (cancel and reduce); pyramiding; automatic
position reversal; same-bar close processing under
process_orders_on_close; FIFO or LIFO close rules; slippage in
ticks; percent, per-contract, cash-per-order and cash-per-trade
commission models; margin calls with liquidation-price projection;
and multi-currency accounts via automatic FX conversion.

3. Intrabar Path Processing: Reproduces the fine-grained behaviors
that make fills match the Strategy Tester — exits evaluate before
market fills, entry orders fill in price-path order (highest long
limit first), stops armed on earlier bars fill at the open on
gap-through, and trailing stops activate only on adverse-first
intrabar geometry.

4. Portfolio and Multi-Engine Workflows: Because an engine is a data
structure you instantiate, one script can run one engine per
symbol (bars delivered via request.security()) and aggregate
equity and trades its own way, keep long and short books side by
side, and compare strategy variants with per-engine performance
tables on one chart.

5. Five Risk Guards: Implements risk_max_drawdown,
risk_max_intraday_loss, risk_max_intraday_filled_orders,
risk_max_cons_loss_days and risk_max_position_size, mirroring
strategy.risk.* teardown semantics — pending orders cancelled,
positions flattened, new orders blocked.

6. Validated, Not Assumed: A 25-probe dual-engine suite runs
TradingView's native strategy engine and PineTunnel on identical
signals on the same chart, then compares every closed trade row by
row. Thirteen probe families pass fully; the one partial result
carries a written residual analysis. Every non-obvious branch in
the source documents the TradingView behavior it reproduces — the
library doubles as documentation of the emulator itself.

7. Built-In Analytics: Implements a 50+ metric statistics engine
(Sharpe, Sortino, Calmar, Martin, Ulcer, VaR/CVaR, p-value,
skewness, kurtosis, risk of ruin, alpha/beta vs an SPY benchmark,
rolling windows, SQN) rendered as a 23-column on-chart performance
table. The analytics derive from the author's own earlier
open-source Deeptest library.

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ KEY FEATURES

Execution Engine

Market, limit, stop and stop-limit entries
TP/SL brackets, trailing stops (trail_price / trail_points + trail_offset)
OCA groups with oca_cancel and oca_reduce semantics
Pyramiding, automatic position reversal, FIFO or LIFO closes
process_orders_on_close same-bar fills

Broker Simulation

Slippage in ticks
Percent, per-contract, cash-per-order, cash-per-trade commissions
Margin calls with liquidation-price projection
Multi-currency accounts, automatic FX conversion
Default sizing: fixed, percent_of_equity, cash

Risk Management

risk_max_drawdown (cash or percent of equity)
risk_max_intraday_loss (cash or percent)
risk_max_intraday_filled_orders
risk_max_cons_loss_days
risk_max_position_size

Intrabar Processing

O → H/L → C walk with adverse-first geometry
Price-path entry fills (highest long limit first)
Gap-through stop fills at the open
Placement-bar arming and re-issued bracket levels

Portfolio and Multi-Engine

One engine per symbol, bars via request.security()
Long and short books side by side
Variant comparison with per-engine performance tables
Custom and synthetic bar feeds via onBar() arguments

Data and Transparency

Every fill, leg, bracket, trail tick and commission readable as values
Untrimmed closed-trade history — no List-of-Trades cap
114 strategy.*-named exports

Analytics

50+ metrics: Sharpe, Sortino, Calmar, Martin, Ulcer, VaR/CVaR,
p-value, skewness, kurtosis, risk of ruin, SQN
23-column performance table with complementary row
Alpha/Beta and Buy & Hold vs an SPY benchmark

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ QUICK START

Basic Usage

Pine Script®


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ EXECUTION MODEL

Purpose

The engine advances one bar at a time under your control, so chart
built-ins, custom feeds and multi-engine setups all follow the same
contract.

Method

1. Call PT.onBar(eng) once per bar, before any order logic. It reads
chart OHLC by default; pass custom values to feed synthetic bars.
2. Run your signal block. PT.entry(), PT.order(), PT.exit(),
PT.close() and PT.cancel() queue or place orders exactly like
their strategy.* counterparts.
3. With process_orders_on_close = true, call PT.onBarPost(eng, open,
high, low, close, time, syminfo.pointvalue) after the signal
block — brackets evaluate intrabar before same-bar market fills,
matching the Strategy Tester's ordering.

Ordering Laws

Exits evaluate before market fills on the same bar
Limit and stop entries arm for the next bar, never the placement bar
A stop placed on bar N is first eligible on bar N+1
A re-issued bracket walks the OLD level on the re-placement bar; the
new level arms for the next bar
cancel_all() clears pending entry orders and preserves exit brackets

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ PARITY VALIDATION

Purpose

Every engine behavior traces to an observed Strategy Tester law, not
an assumption. This section is the heart of the library, because
"trust me, it matches" is worthless in backtesting. Here is exactly
how much was measured, and how.

The Method

Each of the 25 probes is a single script that runs two engines on the
same chart, the same bars, the same signals: TradingView's native
strategy() on one side, a PineTunnel engine with identical
configuration on the other. When the run ends, both dump their
complete closed-trade logs, and a comparator walks every row of both
lists side by side.

The tolerances are deliberately brutal:

Entry/exit times: exact to the millisecond
Prices and quantities: exact to 8 decimal places
Profit: exact to 2 decimal places
Entry/exit order IDs: exact string match

One divergent field on one row fails the entire probe.

The Hunt

When a probe failed, the rule never changed: find the FIRST diverging
row, read the bars before it, and explain exactly why the two engines
disagreed on THAT bar. No papering over. No tweaking until green.
Some hunts took minutes. The worst took days. A few findings are
worth sharing, because they show how deep the rabbit hole goes:

▪ Default sizing is computed at the FILL price, not the signal
close — first exposed by a one-unit quantity mismatch in
default-qty entries.

▪ cancel_all() cancels pending entry orders but PRESERVES exit
brackets — counter-intuitive, confirmed by watching a bracket
fire during the same bar as a close_all.

▪ A re-issued bracket walks the OLD stop level on the re-placement
bar; the new level arms only on the next bar.

▪ Relative levels are recomputed in tick space, not price space —
because in float arithmetic 33862.09 − 2.00 can land one tick
below the Tester's result, and that single tick flipped a fill.

▪ Trailing stops activate only on adverse-first intrabar geometry —
the walk's first move matters, not just the bar's range.

▪ And one negative result we are proud of: three branches that made
individual probes pass while breaking others were REVERTED. An
engine that overfits its own test suite is worthless. Only a fix
that passed every probe at once was allowed to stay.

The Numbers (BINANCE:BTCUSDT, 2H suite)

Market orders (strategy.order): 511/511 rows
Limit entries: 456/456 rows
Stop entries: 527/527 rows
cancel: 176/176 rows
cancel_all: 176/176 rows
Trailing stops (trail_points/trail_offset): 261/261 rows
OCA groups: 615/615 rows
Risk R1 max_drawdown: 12/12 rows
Risk R3 max_intraday_filled_orders: 439/439 rows
Margin liquidation price: 439/439 trades, 0 mismatches across 9,118
price rows
Default entry sizing: 439/439 rows
closedtrades accessors: 439/439 rows
opentrades accessors: 3,271 rows verified
Full main-strategy probe: 486/486 trades
Risk R2 max_intraday_loss: 437/438 rows — see below

Across the passing families, that is over 8,000 closed-trade rows
compared field by field, plus 9,118 margin-liquidation price checks
with zero mismatches — and a net-profit difference of exactly 0.0 on
every passing probe.

The One That Got Away

R2 max_intraday_loss stands at 437/438 rows. We bracketed the
Tester's halt bar by bar, proved that prior-day peaks and all-time
peaks do not count toward its reference equity, and still could not
reconstruct the internal state the tester holds for that final row.
Rather than ship a guess that scores 438/438, we shipped the
analysis and kept the residual documented. One row, disclosed — not
hidden.

Interpretation

On pointvalue-1 symbols (crypto, stocks, forex-style), fills match
the Strategy Tester digit-exact. Futures contract-scale symbols are
fully supported by the engine, but their margin and risk scaling is
not parity-probed.

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ THE strategy.* SURFACE

Read Surface

Account and position: equity, netprofit, openprofit and their
percent forms, initial_capital, account_currency, position_size,
position_avg_price, position_entry_name, grossprofit, grossloss,
wintrades, losstrades, eventrades, avg_trade and family,
max_contracts_held_*, max_drawdown(_percent), max_runup(_percent),
margin_liquidation_price, capital_held
Trade accessors: closedtrades.* and opentrades.* with entry/exit
id, price, time, bar_index, comment, size, profit, profit_percent,
commission, max_drawdown(_percent), max_runup(_percent)
Conversion: convert_to_account, convert_to_symbol,
default_entry_qty

Order Surface

entry, order, exit, close, close_all, cancel, cancel_all — with the
same parameter names as their strategy.* counterparts, plus engine
extensions (oca_type on exit, signal_close, immediately on close).

Constants

long, short, fixed, cash, percent_of_equity, direction.long /
direction.short / direction.all, oca.none / oca.cancel / oca.reduce,
commission.percent / cash_per_order / cash_per_contract

Configuration

makePTConfig() mirrors the strategy() declaration — initial_capital,
default_qty_type and value, currency, commission_type and value,
slippage, pyramiding, process_orders_on_close, direction filter,
margin_long / margin_short, close_entries_rule, risk_free_rate —
plus engine extensions: qty_step lot flooring,
fill_limits_assumption and history_cap.

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ PERFORMANCE TABLE

Main Table — organized into three sections:

Performance Metrics (Cols 0-6): Net Profit, Payoff, Sample Size,
Profit Factor, CAGR, Expectancy (or R-Expectancy), Monthly
Trade Statistics (Cols 7-14): Avg Duration, Max CW, Max CL, Win, BE,
Loss, Frequency, Exposure
Risk Metrics (Cols 15-22): Sharpe, Sortino, Max DD, RoR, R², MAR,
CVaR, p-value

Complementary Row — compounding effect, average win/trade/loss,
Martin ratio, rolling expectancy and Sharpe/Sortino ranges, win/loss
durations, max/min equity, alpha, Buy & Hold, beta, current
drawdown, SQN, kurtosis, skewness, VaR, Ulcer Index.

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ IMPLEMENTATION NOTES

Engine Source: All state lives on the PTEngine object — orders,
legs, brackets, closed trades, equity series and risk state. onBar()
reads chart OHLC by default and accepts custom values for synthetic
or multi-symbol feeds.

Calculation Timing: Analytics compute once on
barstate.islastconfirmedhistory to minimize runtime overhead; the
table renders on the last bar.

Limitations: Parity is probe-validated on pointvalue-1 symbols
(crypto, stocks, forex-style); futures contract-scale margin and risk
scaling is not parity-probed. R2 max_intraday_loss carries one
documented residual row (437/438). alert_message arguments are stored
on fills — firing alert() stays in the consuming script. Engines must
be created at a single makePT() call site (a Pine library constraint).
The analytics benchmark uses one request.security() call for SPY.

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ QUICK NOTES

➙ This library was developed through months of dual-engine parity
probing against the Strategy Tester. Every behavior law in the
engine was observed first and reproduced second.

➙ The entire codebase is thoroughly documented inline. If you are
curious how a fill, a trail tick or a risk guard works, dive into
the source — it is written to be read and learned from.

➙ This description focuses on usage and concepts rather than
exhaustively listing every exported type and function. The library
source is thoroughly documented inline — explore it to understand
implementation details and internal logic.

➙ The call order is the contract: PT.onBar() before order logic,
PT.onBarPost() after the signal block when process_orders_on_close
is enabled.

➙ Risk-guard type strings are "cash" and "percent". Default engine
configuration mirrors the strategy() declaration defaults.

➙ Beyond backtesting, this library serves as a learning resource.
Study the source to understand broker emulation, order lifecycle
design and the fine mechanics of the Strategy Tester itself.

➙ No backtest is a promise. A high Sharpe ratio with a small sample
is misleading; a deep drawdown during a market crash may be
acceptable. Evaluate your strategy contextually, not by threshold
alone.

➙ Screenshots shown in this publication are solely for visual
representation of the performance table and plots. Please do not
compare your strategy's performance with the metrics shown — they
are illustrative examples only, not performance targets.

Snapshot

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ PICK YOUR WORKFLOW — WHERE DO YOU START
Pine Script®


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ HOW-TO

Five steps from an empty indicator to a full backtest:

Pine Script®


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ CREDITS

Author: Fractalyst
Analytics lineage: derived from the author's own open-source Deeptest
library.
Community: Inspired by the PineCoders community initiative,
encouraging developers to contribute open-source libraries and
continuously enhance the Pine Script ecosystem for all traders.

If you find PineTunnel valuable in your trading journey, feel free to
use it in your indicators and give a shoutout to Fractalyst — your
recognition directly supports ongoing development and open-source
contributions to Pine Script.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
█ DISCLAIMER

This library is provided for educational and research purposes. Past
performance does not guarantee future results. Always test thoroughly
and use proper risk management. The author is not responsible for any
trading losses incurred through the use of this code.

Haftungsausschluss

Die Informationen und Veröffentlichungen sind nicht als Finanz-, Anlage-, Handels- oder andere Arten von Ratschlägen oder Empfehlungen gedacht, die von TradingView bereitgestellt oder gebilligt werden, und stellen diese nicht dar. Lesen Sie mehr in den Nutzungsbedingungen.