millionchart
فارسی

Lesson 16: Results that look too good

Why a signal or a strategy's result can use information the bar did not have, how the engine protects you by default, and what the warnings mean.

Lesson 16 of 18. The most expensive mistake in a script is not a crash. It is a result that looks wonderful because it quietly used something the bar could not have known. Two things cause it: a signal that repaints (it changes after the bar closed) and a signal that leaks (it reads the future). This lesson shows how each happens, what the engine does by default, and what its warnings mean.

The default is the closed bar#

When a script reads another timeframe, for example the hourly trend while the chart shows 5 minutes, the question is which hourly bar to read. The default is the last hourly bar that has closed.

Cuprel
indicator "Hourly trend"
    edition = 1
    display = price

let trend = data.series("60", tech.ema(close, 20), symbol = symbol.id)
plot(trend, name = "Hourly EMA")

On the 5-minute bar that opens at 10:35, the script sees the hourly bar of 09:00 to 10:00. It does not see the hourly bar that is still forming, because that bar's close does not exist yet. The line moves once an hour, when an hourly bar closes, and the numbers it shows on past bars are the numbers it would have shown at the time.

data.series(timeframe, expression, symbol = ...) computes the expression on the other timeframe's bars and brings the result back to the chart's bars. Other timeframes and symbols has the details.

Asking for the forming bar#

live = true asks for the hourly bar that is still forming. That only matters on the newest bar of the chart, where the hour is still open: there the value moves with every tick until the hour closes, and a condition built on it can switch on and off. On the bars of the history the hour has long closed, so the read gives the same numbers as the default. That makes it tempting and treacherous at once: the history looks stable, and the live bar does not. It is sometimes what you want, for instance a dashboard that shows the current hourly value. The engine tells you:

Cuprel · warning W-LIVE-READ
indicator "Hourly trend, forming bar"
    edition = 1
    display = price

let trend = data.series("60", tech.ema(close, 20), symbol = symbol.id, live = true)
plot(trend, name = "Hourly EMA, live")

A signal you act on should not depend on a live read.

Asking for the future#

future = true goes further. On the bars of a day it shows the day's final figure, the high of the whole day on its first bar. Nothing in real trading can know that on the morning.

Cuprel · warning W-FUTURE-READ
indicator "Day high, final"
    edition = 1
    display = price

let day_high = data.series("1D", high, symbol = symbol.id, future = true)
plot(day_high, name = "Day high")

If you shift the value back by one day, the read is harmless: yesterday's high is known all day today.

Cuprel
indicator "Yesterday's high"
    edition = 1
    display = price

let yesterday_high = data.series("1D", high[1], symbol = symbol.id, future = true)
plot(yesterday_high, name = "Yesterday's high")

For the previous day, week or month there is a shorter way that needs no request at all: prior(day).high. Trading day and sessions shows it.

A strategy may not use future = true at all. A strategy that trades on tomorrow's prices proves nothing, so the checker refuses it:

Cuprel · error E3132
strategy "Look-ahead strategy"
    edition = 1
    display = price

let day_high = data.series("1D", high, symbol = symbol.id, future = true)
if close > day_high
    orders.enter("Long", side.long)
plot(day_high, name = "Day high")

The bar that has not closed#

The newest bar of a chart is always still forming, even when the market is closed. Its close is the latest price and it changes with every tick, so a condition such as close > open can be true now and false a second later. A signal built on it can appear and then vanish. That is repainting.

confirmed(x) gives x on a bar that has closed and na on the forming bar. Use it to draw only what is final:

Cuprel
indicator "Closed bars only"
    edition = 1
    display = price

let average = tech.sma(close, 20)
let strong = close > open and close > average
mark.shape(
    confirmed(strong),
    name = "Strong close",
    shape = circle,
    place = below,
    color = color.green,
)

For an alert, once = "bar_close" does the same: it rings only after the bar has closed (alerts on your own scripts are switched off in this release, see Alerts that say something).

Signals that come late#

Some answers can only be known after the fact. A pivot high is a bar whose high is the highest of the five bars on each side; the five bars to its right must close before it is a pivot. tech.pivot_high(high, 5, 5) reports it on the bar where it becomes known, five bars after the pivot itself. If you draw the pivot back on the bar it belongs to, the picture looks convincing, but at the time the mark did not exist. Draw it where it is known, or accept that it arrives late.

A short checklist#

  1. Read other timeframes with the default. Use live = true only for something you will look at, not trade.
  2. Never use future = true on a value you did not shift back. In a strategy it is an error.
  3. Put a condition you will act on behind confirmed(...) or once = "bar_close".
  4. Remember that pivots, and anything that needs bars to its right, arrive late.
  5. If a strategy's result looks too good, shorten the history, change the symbol and run it again. A result that depends on one lucky stretch is not a result.

Try changing#

  • In "Hourly trend", read "240" instead of "60" on a 15-minute chart: the line now steps once every four hours.
  • In "Hourly trend, forming bar", remove live = true: the editor no longer lists the note W-LIVE-READ.
  • In "Day high, final", change high to high[1]: the note W-FUTURE-READ goes away, because yesterday's high is known all day today.
  • In "Closed bars only", remove confirmed(...) around strong and watch the newest bar: its mark can appear and disappear while the bar is forming.

Checkpoint#

  • You know which bar a read of another timeframe gives by default, and what live = true and future = true change.
  • You can keep a signal to closed bars with confirmed(...).
  • You know why a pivot is reported late, and where to draw it.
  • You can apply the checklist above before you trust a result.

See also: Other timeframes and symbols, Alert keys and options, Your first strategy.

Next: Lesson 17: Staying inside the limits