Backtesting guide

Look-ahead bias: audit when information became available

Look-ahead bias occurs when a historical decision uses information that was unavailable at that moment. Audit the version of each input and its availability time, then check when an order could execute. A confirmed chart bar cannot make an unpublished earnings figure or an unfinished daily close available earlier.

Read the practical guide

Key Takeaways

  • Keep event time, public release time and first permitted execution time separate. A quarter-end date is not an earnings announcement.
  • A financial revision belongs to its own release time. Replacing every historical value with the latest revision can change past signals.
  • For confirmed higher-timeframe prices, the position of the history offset matters as much as the lookahead setting.
  • A trade CSV records simulated outcomes. Retain an input-availability ledger separately to investigate leakage.

Use three clocks, then add the data delivery delay

For every input, record what period it describes, when that version became public, and when your strategy could act. Set available_at = max(public_release_at, vendor_received_at) + processing_delay. Require available_at <= decision_at, then place execution no earlier than the first permitted opportunity after submission. These are audit rules; the actual latency and eligible session must come from your data and execution setup.

Use three clocks, then add the data delivery delay
ClockWhat it meansHypothetical earnings record
Event or period timeThe economic period being measuredQuarter ends March 31, 2026
Public release timeThis version first becomes publicApril 30, 2026 at 16:05 America/New_York
First tradable timeYour order can execute under the chosen session policyMay 1 at 09:30 for a regular-session-only opening-order policy
  • Assume delivery at 16:05:02 and one second of processing: the input is available at 16:05:03. A decision at 16:05:01 fails the audit even though the announcement is already public.
  • The next regular-session opening is only an eligibility boundary. It does not guarantee an opening-price fill. A strategy that evaluates only at the next daily close must wait longer.
  • Store timezone offsets and normalize comparisons to UTC. If only a release date is known, mark the exact intraday availability as unknown or apply a stated conservative delay; do not silently assign midnight.

Test original financial values against later revisions

Use a versioned record keyed by issuer, metric, period, units, accounting basis and release. At each decision, select the latest version whose available_at is no later than decision_at. Keep older versions. The following invented EPS example uses a buy condition of EPS > $1.10 and ignores all other entry conditions.

Test original financial values against later revisions
DecisionValue available thenCorrect conditionFailure when history is overwritten
April 1, before the releaseNo EPS for this quarterUnknown: skip this conditionUsing $1.20 at quarter end creates a premature buy
May 1, after the original release$1.20, first published April 30True: $1.20 > $1.10A later $0.90 revision incorrectly removes this signal
June 2, after a June 1 revision$0.90, revised versionFalse: $0.90 <= $1.10Continuing to use $1.20 ignores information now available
  • Compare two runs with frozen rules: one using the original values available at each decision, one using the latest downloaded history. List the exact decisions that change; a similar aggregate profit does not clear the data.
  • A filing date can establish availability of that filing, but an earlier company announcement may have disclosed the same figure. Match the particular fact and accounting definition before assigning an earlier release.
  • The SEC frames API chooses a last-filed fact that fits a calendar period. That is not an as-of-decision snapshot. Preserve accession and release evidence rather than using the current frame as a historical information set.
  • A release-timed series is not automatically a revision archive. If original versions or delivery times are unavailable, report that limitation; adding a fixed quarterly lag cannot reconstruct missing vintages.

Inspect the TradingView data function before using fundamentals

TradingView documents request.financial() as periodic data whose timing is independent of the precise release date. request.earnings() supplies earnings events on report dates. Neither a fiscal-period label nor a plotted value establishes the intraday availability and revision history required by your audit.

  • For an earnings-event rule, reconcile the plotted event against the issuer release, timezone, session and the exact earnings field. A reported EPS value and a standardized financial EPS metric may use different definitions.
  • For a fundamental screen, keep external release and vintage evidence. If Pine cannot reproduce the required historical information set, treat the affected backtest as unverified or move that part of the test to a versioned dataset.

Compare a leaking HTF request with a confirmed one

Run this diagnostic indicator on a standard 15-minute chart with a daily higher timeframe. The red series deliberately exposes the current daily final close too early on historical bars. The teal series uses the previous completed daily close. It places close[1] inside request.security(); shifting the returned series by one chart bar is a different operation. This example plots both series and places no orders.

Pine Script v6: deliberately biased red series versus confirmed teal series
//@version=6
indicator("HTF availability audit", overlay = true)
string higherTf = input.timeframe("1D", "Higher timeframe")
if timeframe.in_seconds(higherTf) <= timeframe.in_seconds()
    runtime.error("Select a timeframe above the chart timeframe.")
// Deliberately biased comparison: never use this series for orders.
float leaked = request.security(syminfo.tickerid, higherTf, close, lookahead = barmerge.lookahead_on)
// Previous completed HTF bar, available at the next HTF period.
float available = request.security(syminfo.tickerid, higherTf, close[1], lookahead = barmerge.lookahead_on)
plot(leaked, "Invalid historical preview", color.red)
plot(available, "Previous confirmed HTF close", color.teal, 2)
  • Synthetic check: yesterday closes at 100 and today eventually closes at 110. On an early historical bar today, the red request shows 110 while the teal request shows 100. A threshold above 105 therefore passes only for the leaked input.
  • The correction pairs a one-HTF-bar offset with lookahead_on. Using lookahead_off alone avoids this historical preview but can still read a developing HTF value in realtime.
  • The guard rejects equal or lower timeframes. Missing prior history can produce na. Use the previous confirmed value only when that delay matches the strategy definition; this is not a method for anticipating the current daily close.
  • Record both plots during a realtime HTF period, then reload after it closes. The red history can change; the teal request keeps confirmed HTF timing. Feed corrections can still revise underlying history. A syntax pass does not validate data vintages or strategy performance.

Separate a timing violation from a performance difference

Create one audit row for each decision and input dependency, with decision ID, source/version, available_at, decision_at, order submission and simulated fill time. For multiple inputs, the latest availability controls the decision. A missing timestamp is unknown, not a passing observation.

Separate a timing violation from a performance difference
Hypothetical rowAvailability relative to decisionAudit result
A2 seconds beforePass
B8 seconds afterViolation
C1 day afterViolation
DExactly equal, with processing already includedPass
EMissing release or delivery evidenceUnknown
  • Timing violation rate = violating rows / rows with complete timing evidence. Here it is 2 / 4 = 50%; coverage = 4 / 5 = 80%. If no row has complete evidence, the rate is undefined, not zero. These are independent audit calculations, not Backtest Deep Report fields.
  • Run the corrected strategy again. Do not merely delete suspect trades from the old CSV: changed signals can change positions, sizing and later opportunities. Compare decision IDs, changed entries, trade count and net results under identical costs.
  • A lower profit after correction is a sensitivity result. The timing ledger establishes the violation; a profit difference alone does not identify its cause or prove the corrected strategy is unbiased.

Reconcile exported trades with the input ledger

Backtest Deep Report parses a compatible TradingView List of Trades CSV with trade IDs, entry/exit types, signal labels, dates, prices, quantities and P&L. Its parsed trade records include entryTime and exitTime, but not the release timestamp or financial vintage behind each signal. Preserve those separately and verify the export timezone before joining records.

  • Save the original and corrected source, inputs, chart symbol, session, timeframe, costs and date window with each export. Check parsed trade count, timestamps and net P&L against TradingView before comparing reports.
  • A fill timestamp is not necessarily the decision timestamp. TradingView normally calculates strategies at bar close and fills a new market order on the next available tick. Other calculation and fill settings change that relationship.
  • Use the strategy signal log to connect the availability ledger to trade IDs. The CSV alone cannot prove that a data release preceded every decision; the report does not compute the timing violation rate above.

Related Pineify workflow

Compare the exports after repairing the data timing

Use Backtest Deep Report to inspect each compatible TradingView trade CSV. Ask Pine Script AI Coding Agent for a reviewable code change using your stated timeframe, confirmation and execution rules.

Backtest Deep Report

Inspect parsed trades and performance for the original and corrected exports.

Analyze a trade CSV
Pine Script AI Coding Agent

Describe the intended input timing and request a code revision you can inspect.

Review the Pine logic

Boundary: Neither workflow certifies point-in-time data or reconstructs unpublished financial vintages. Keep the availability audit separately and validate generated code and its behavior before relying on results.

This page is educational and does not provide investment advice. Backtests are hypothetical, depend on their data and assumptions, and do not guarantee future results. Trading can result in substantial loss.

Frequently Asked Questions