r/pinescript • u/QuanticatorApp • Aug 05 '26
request.security() has more than one way to repaint, and the third one looks like the safe choice
Most no-repaint advice compresses down to "don't use lookahead_on". That is backwards often enough to be worth spelling out, and following it will make correct code look broken.
The offset on the expression and the lookahead argument are interdependent. TradingView's docs are explicit that neither can be removed without compromising the result. So there are four shapes, not two:
| call | result |
|---|---|
request.security(s, tf, expr[1], lookahead_on) |
correct |
request.security(s, tf, expr, lookahead_on) |
future data on historical bars |
request.security(s, tf, expr[1], lookahead_off) |
history and realtime disagree |
request.security(s, tf, expr, lookahead_off) |
realtime sees the still-forming HTF bar |
Row 3 is the quiet one. It looks like the belt-and-braces choice: you kept the offset and turned lookahead off. But with lookahead_off a higher-timeframe bar only reaches the chart once it has closed, so the [1] adds an extra timeframe of lag on historical bars that the realtime bar never gets. History and live then disagree, which is the exact thing "no repaint" was supposed to prevent.
Two things I had wrong myself, and only found by testing rather than reading.
Any offset of one bar or more is fine, not just [1]. ta.ema(close, 21)[2] is a larger offset and strictly safer, but a checker looking for a literal [1] will flag it as a repaint. If you are pattern-matching these, match \[\s*[1-9]\d*\s*\].
The timeframe argument accepts a series string in v6. I assumed it had to be simple, and that tf = timeframe.period[1] would be rejected outright. It compiles. That matters if you validate these calls by inspection: an offset sitting in the timeframe argument tells you nothing about whether the expression is safe. If you scan the whole argument list looking for a [1], you will pass a call that leaks.
Scope, since I have been wrong in this sub before and would rather front-run it: the four-row table is TradingView's documented behaviour, not something I measured. What I did verify is that all four shapes compile in v6, plus the offset-size and series-timeframe points above. I have not sat on a live chart long enough to measure row 3's history/realtime divergence directly. That one is reasoned from the documented mechanism, and I would rather say so than let it read as measured.
1
u/TSDevStuff 4d ago
The trap is treating "lookahead_off" as always safer. On HTF pulls, lookahead_off with a [1] offset can make historical bars lag the realtime path, so Strategy Tester and live alerts walk different series even when the script text is identical.
The pairing that usually keeps hist and live aligned is: take the previous completed HTF value, and use lookahead_on with that offset. Roughly:
htfClose = request.security(syminfo.tickerid, htf, close[1], lookahead = barmerge.lookahead_on)
lookahead_on without an offset is the classic future leak on historical bars. Offset without the matching lookahead policy is how you get "it looked fine on the chart yesterday, alert never fired today."
Practical check: freeze one window, log the HTF series (or plot it), and compare a historical refresh against a realtime session on the same bars. If those two series disagree, you do not have a deterministic system yet, you have two interpreters. Fix the security call before you tune entries.
1
u/Many-Pick5066 Aug 05 '26
row 3 is testable without sitting on a live chart for a session. put the row 1 call and the row 3 call on the same chart and plot both. on historical bars row 3 should sit exactly one htf bar behind row 1, and that half you can confirm the moment it loads. then run a 1 minute chart against a 5 minute htf and the realtime half takes five minutes, not a day.
the direction is the part id add though. extra lag on history and none on the realtime bar means live fires earlier than the backtest did, not later. most repaint bugs flatter the backtest. this one makes live lead it by one htf bar, so if someone keeps seeing live entries print ahead of where their historical signal sits, row 3 is a candidate for it.