Other timeframes and symbols
Read a higher timeframe from a script with data.series, what the default read means, when live and future change it, and why another symbol is refused.
A script sometimes needs a number from a different timeframe: the daily high on a 5-minute chart, the hourly trend under a minute chart. data.series brings it back to the chart's bars.
data.series#
data.series(timeframe, expression, symbol = ...) runs the expression on the bars of the other timeframe and gives the result on every bar of the chart.
indicator "Hourly average on any chart"
edition = 1
display = price
input tf: tf = "60", name = "Timeframe"
input length: int = 20, name = "Length", min = 2
let other = data.series(tf, tech.ema(close, length), symbol = symbol.id)
plot(other, name = "Average on the other timeframe", color = color.orange)symbol.idis the chart's own symbol.- The timeframe is a text. A whole number is minutes:
"15","60"(one hour),"240"(four hours). A count and a unit works too:"15m"is fifteen minutes (a smallm),"1h"and"4h"are hours,"1D"a day,"1W"a week and"1M"a month (a capitalM:"15M"is fifteen months)."D","W"and"M"alone are one day, one week and one month. An input of the typetfcan be given straight todata.series. - The expression is anything the script can compute: a price, an indicator, a condition. It is computed over the other timeframe's bars, with their own history, so
tech.ema(close, length)above is the hourly EMA. - The expression can be a tuple:
data.series("1D", (high, low), symbol = symbol.id)andlet (day_high, day_low) = ....
The timeframe has to be above the chart's#
A timeframe must be longer than the chart's own. A timeframe that is not longer does not stop the script: it reads the chart's own bars, so the series equals the chart's, and the engine says so with a warning (W-ENGINE-LOWER-TF). When the market server has no weekly or monthly bars, they are built from the chart's own bars, with a warning, and reach back only as far as the chart does.
A text that is not a timeframe at all stops the script, and never gives the chart's own bars under another name. A text written in the script is refused when the script is checked, with the line; a text that is computed, or that comes from an input, is refused on the bar where it is read:
indicator "A timeframe that does not exist"
edition = 1
plot(data.series("1hh", close, symbol = symbol.id))The message says what the chart does know: minutes as a number, or a count and a unit. The same reading is used by bar.time_in, bar.close_time_in, tf.changed and tf.to_seconds.
Which bar do you get?#
By default you get the last bar of the other timeframe that has closed. On the 5-minute bar that opens at 10:35, the hourly value is the one from the hour that ended at 10:00. This is the only choice that gives the same number now and in the history, so it is the default and the safe one.
| You write | You get |
|---|---|
| nothing | the last closed bar of the other timeframe |
live = true | on the newest chart bar, the hourly bar that is still forming, so its value moves until it closes; on the bars of the history it gives the value of the default read (warning W-LIVE-READ) |
future = true | the bar's final figure from its first chart bar on, a look-ahead (warning W-FUTURE-READ; refused in a strategy, E3132) |
indicator "The forming hour"
edition = 1
display = price
let forming = data.series("60", close, symbol = symbol.id, live = true)
plot(forming, name = "Close of the forming hour")future = true does no harm when the value is already known at the start of the bar, such as the open of the day, or is shifted back:
indicator "Open of today, known all day"
edition = 1
display = price
let day_open = data.series("1D", open, symbol = symbol.id, future = true)
plot(day_open, name = "Open of the day")The open of a bar is known when the bar opens, so reading the day's open on every bar of that day is fine. Results that look too good explains why the others are risky.
Where the windows of a timeframe are#
On one chart a timeframe has one set of windows, whichever way you read it. The hourly and four-hourly bars the market server sends keep the broker's own phase (a four-hour bar opens at 00:00, 04:00 and so on in UTC), and the bar that is still forming after the last of them keeps that phase too. A day, a week and a month begin where the symbol's trading day begins (Trading day and sessions). So close[1] with future = true and the default read of the same timeframe give the same series: shifting a look-ahead back by one bar is exactly the closed bar, with no hours of overlap and nothing seen early.
Without bars from the server the engine builds the timeframe from the chart's own bars, counting from the trading day's start, and says so with W-ENGINE-HTF-RESAMPLED.
What stays the same for the whole run#
The symbol and the timeframe of a request must be the same on every bar. An input, a literal and the chart's own constants (symbol.id, tf.id) qualify. A price or a value that changes from bar to bar does not:
indicator "A timeframe that moves"
edition = 1
let other = data.series(if close > open then "60" else "240", close, symbol = symbol.id)
plot(other)A function may take the timeframe as a parameter, as long as every call passes the same one on every bar:
indicator "A helper for another timeframe"
edition = 1
display = price
input tf: tf = "240", name = "Timeframe"
fn on_timeframe(frame: tf, value: float): float = data.series(frame, value, symbol = symbol.id)
plot(on_timeframe(tf, high), name = "High", color = color.green)
plot(on_timeframe(tf, low), name = "Low", color = color.red)A request keeps its own memory for each place it is called from, so it cannot be inside a loop. Write each call out, or call it once before the loop:
indicator "A request in a loop"
edition = 1
mut total = 0.0
for i in 0 to 2
total += data.series("60", close, symbol = symbol.id)
plot(total)A script may use at most 20 different symbol-and-timeframe pairs; 22 calls that read the same timeframe are one pair. More is E-REQ-LIMIT.
Another symbol#
Data for a symbol other than the chart's is not available in this release. A request for one stops the script with an error that names the symbol. If you write the symbol in the script, you also have to declare it with uses, so a reader and the platform know what the script wants:
indicator "A symbol that is not declared"
edition = 1
plot(data.series("60", close, symbol = "EURUSD"))indicator "A symbol that has no data here"
edition = 1
uses "EURUSD"
plot(data.series("60", close, symbol = "EURUSD"))No empty series is ever made up for a symbol with no data: the script stops, so a result is never a silent gap. An input of the type symbol is declared by its default and is refused in the same way when its value is not the chart's own symbol.
See also: Results that look too good, Trading day and sessions, Limits and budgets.