Handling Timeframes in Pine Script v6: timeframe.period and timeframe.multiplier Explained
When building indicators or strategies that must behave differently depending on whether the user is viewing a 1-minute chart versus a daily chart, Pine Script v6 provides two essential built-in variables: timeframe.period and timeframe.multiplier. Understanding how these two variables work together allows you to write scripts that dynamically adapt their logic to any chart resolution without hardcoding timeframe assumptions.
1. What Are timeframe.period and timeframe.multiplier?
Pine Script v6 exposes several built-in timeframe.* variables that describe the chart's current resolution at runtime. The two most important for dynamic adaptation are:
| Variable | Type | Description | Example (1H chart) |
|---|---|---|---|
timeframe.period |
simple string | The full timeframe string of the current chart (e.g., "60", "D", "W") | "60" |
timeframe.multiplier |
simple int | The numeric multiplier portion of the current timeframe | 60 |
For a 1-hour chart, timeframe.period returns "60" (because Pine Script uses minutes as the unit for intraday timeframes — there is no "H" unit). timeframe.multiplier returns the integer 60. For a daily chart, timeframe.period returns "D" and timeframe.multiplier returns 1.
2. Timeframe Classification Booleans
Pine Script v6 also provides a set of boolean variables to classify the current chart's timeframe category. These are evaluated once at script load time (they are simple bool values):
| Variable | True When |
|---|---|
timeframe.isseconds |
Chart is a seconds-based timeframe |
timeframe.isminutes |
Chart is a minutes-based timeframe (includes hourly, e.g., 60, 240) |
timeframe.isdaily |
Chart is a daily timeframe |
timeframe.isweekly |
Chart is a weekly timeframe |
timeframe.ismonthly |
Chart is a monthly timeframe |
Important: There is no timeframe.ishours variable in Pine Script v6. Hourly charts are classified under timeframe.isminutes because they are represented as minute-based timeframes (e.g., 60 minutes = 1 hour).
3. Logic Flow: How the Script Adapts
The following diagram illustrates how a script uses timeframe.isminutes, timeframe.isdaily, and timeframe.multiplier to select an appropriate EMA length dynamically.
4. Practical Example: Dynamic EMA Length Based on Timeframe
The core use case is scaling indicator parameters proportionally to the chart's resolution. For example, a 20-period EMA on a daily chart represents 20 days of data. On a 5-minute chart, you might want to scale that to represent a similar amount of market time. Below is a complete, verified Pine Script v6 example.
🔽 [Click to expand] View Full Pine Script Code
//@version=6
indicator("Dynamic Timeframe Adapter", overlay = true)
// ─────────────────────────────────────────────────────────────
// SECTION 1: Read current chart timeframe properties
// ─────────────────────────────────────────────────────────────
// timeframe.period → simple string: the full TF string (e.g. "5", "60", "D")
// timeframe.multiplier → simple int: the numeric part of the TF (e.g. 5, 60, 1)
string tfPeriod = timeframe.period
int tfMult = timeframe.multiplier
// ─────────────────────────────────────────────────────────────
// SECTION 2: Classify the timeframe using built-in booleans
// ─────────────────────────────────────────────────────────────
// timeframe.isminutes is true for ALL minute-based TFs including hourly (60, 240)
// There is NO timeframe.ishours in Pine Script v6
bool isMinutes = timeframe.isminutes
bool isDaily = timeframe.isdaily
bool isWeekly = timeframe.isweekly
bool isMonthly = timeframe.ismonthly
// ─────────────────────────────────────────────────────────────
// SECTION 3: Compute a dynamic EMA length
// ─────────────────────────────────────────────────────────────
// Base reference: 20 bars on a daily chart = 20 trading days
// For intraday charts, scale by how many bars fit in one trading day
// A standard trading day ≈ 390 minutes (US equities)
// For weekly/monthly, scale up proportionally
// NOTE: timeframe.multiplier is simple int, so arithmetic is safe.
// int(math.max(...)) is used to ensure the result is a valid int
// and satisfies the simple int requirement of ta.ema().
int baseLength = 20 // baseline EMA length (const int)
// Calculate bars per day based on timeframe
// For minutes: barsPerDay = 390 / tfMult (integer division via int())
// We use math.max to prevent division producing 0 or negative values
int barsPerDay = isMinutes ? int(math.max(1, 390 / tfMult)) : 1
// Scale the EMA length:
// - Minutes: multiply base by barsPerDay to get ~20 days of bars
// - Daily: use base directly
// - Weekly: compress (base / 4, minimum 2)
// - Monthly: compress further (base / 20, minimum 2)
int dynamicLength = (
isMinutes ? int(math.max(2, baseLength * barsPerDay)) :
isDaily ? baseLength :
isWeekly ? int(math.max(2, baseLength / 4)) :
isMonthly ? int(math.max(2, baseLength / 20)) :
baseLength
)
// ─────────────────────────────────────────────────────────────
// SECTION 4: Calculate and plot the dynamic EMA
// ─────────────────────────────────────────────────────────────
// ta.ema() requires simple int for its length parameter.
// dynamicLength is derived from simple int (timeframe.multiplier)
// and const int (baseLength), so it satisfies the simple int requirement.
float dynamicEma = ta.ema(close, dynamicLength)
plot(dynamicEma,
title = "Dynamic EMA",
color = color.new(color.orange, 0),
linewidth = 2)
// ─────────────────────────────────────────────────────────────
// SECTION 5: Display a label on the last bar showing TF info
// ─────────────────────────────────────────────────────────────
// Build an informational string using str.tostring()
// str.tostring(value, "#.##") formats a float to 2 decimal places
// str.tostring(intValue) converts an int to its string representation
string tfCategory = (
isMinutes ? "Minutes" :
isDaily ? "Daily" :
isWeekly ? "Weekly" :
isMonthly ? "Monthly" :
"Other"
)
// Compute bars-per-day as float for display purposes
float barsPerDayDisplay = isMinutes ? 390.0 / tfMult : 1.0
string infoText = "TF: " + tfPeriod +
" | Category: " + tfCategory +
" | Mult: " + str.tostring(tfMult) +
" | Bars/Day: " + str.tostring(barsPerDayDisplay, "#.##") +
" | EMA Len: " + str.tostring(dynamicLength)
// Draw a label only on the most recent bar
if barstate.islast
label.new(
x = bar_index,
y = dynamicEma,
text = infoText,
style = label.style_label_left,
color = color.new(color.orange, 80),
textcolor = color.black,
size = size.large)
5. Key Formula: Scaling EMA Length for Intraday Charts
The mathematical relationship used to scale the EMA length for intraday timeframes is:
$$\text{dynamicLength} = \text{baseLength} \times \left\lfloor \frac{390}{\text{timeframe.multiplier}} \right\rfloor$$Where:
- $\text{baseLength} = 20$ (the reference number of daily bars)
- $390$ is the approximate number of minutes in a standard US equity trading session
- $\text{timeframe.multiplier}$ is the chart's bar size in minutes (e.g., 5 for a 5-minute chart, 60 for a 1-hour chart)
- $\lfloor \cdot \rfloor$ denotes the floor (integer truncation) operation, implemented via
int()
For a 5-minute chart: $\text{dynamicLength} = 20 \times \lfloor 390 / 5 \rfloor = 20 \times 78 = 1560$ bars
For a 60-minute chart: $\text{dynamicLength} = 20 \times \lfloor 390 / 60 \rfloor = 20 \times 6 = 120$ bars
For a daily chart: $\text{dynamicLength} = 20$ bars (used directly)
6. Using timeframe.period with request.security()
Another powerful application of timeframe.period is constructing higher-timeframe requests dynamically. Because timeframe.period is a simple string, it can be used directly in request.security() calls. The following snippet demonstrates a non-repainting higher-timeframe close fetch using the official pattern:
🔽 [Click to expand] View Higher-Timeframe Request Example
//@version=6
indicator("HTF Close via timeframe.period", overlay = true)
// ─────────────────────────────────────────────────────────────
// Fetch the confirmed (non-repainting) close from a user-selected
// higher timeframe. The official non-repainting pattern uses
// close[1] + barmerge.lookahead_on.
// ─────────────────────────────────────────────────────────────
// User selects a higher timeframe via input
string htfInput = input.timeframe("D", title = "Higher Timeframe")
// Non-repainting HTF close:
// close[1] shifts to the previous confirmed bar
// barmerge.lookahead_on maps the value to the start of the HTF period
// Together they produce a stable, non-repainting series
float htfClose = request.security(
syminfo.tickerid,
htfInput,
close[1],
lookahead = barmerge.lookahead_on)
// Also display the current chart's timeframe period string as a label
// timeframe.period is a simple string — safe to use in str.* functions
string currentTF = timeframe.period
plot(htfClose,
title = "HTF Close (Non-Repainting)",
color = color.new(color.blue, 0),
linewidth = 2)
// Show current TF info on the last bar
if barstate.islast
label.new(
x = bar_index,
y = htfClose,
text = "Chart TF: " + currentTF + " | HTF: " + htfInput,
style = label.style_label_left,
color = color.new(color.blue, 80),
textcolor = color.white,
size = size.small)
7. Common Pitfalls and Anti-Patterns
| Pitfall | Wrong | Correct |
|---|---|---|
| Using "H" as an hour unit in timeframe strings | "1H" |
"60" (60 minutes) |
| Checking for hourly with a non-existent variable | timeframe.ishours |
timeframe.isminutes and timeframe.multiplier >= 60 |
| Passing series int to ta.ema() length | var int len = 20; if cond: len := x; ta.ema(close, len) |
Derive length from simple int sources only (e.g., timeframe.multiplier, input.int()) |
| Formatting floats with str.tostring(math.round(v, 2)) | str.tostring(math.round(value, 2)) |
str.tostring(value, "#.##") |
| Repainting HTF request | request.security(sym, tf, close) |
request.security(sym, tf, close[1], lookahead = barmerge.lookahead_on) |
8. Conclusion
timeframe.periodreturns the current chart's timeframe as a simple string (e.g.,"5","60","D"), whiletimeframe.multiplierreturns the numeric portion as a simple int — both are evaluated once at script load and are safe to use asta.ema()length inputs after integer casting.- Hourly charts are classified under
timeframe.isminutes(not a non-existenttimeframe.ishours), and hour-based timeframe strings use minute notation (e.g.,"60"for 1 hour,"240"for 4 hours). - The correct pattern for formatting float values to two decimal places in Pine Script v6 is
str.tostring(value, "#.##")— notstr.tostring(math.round(value, 2)).
Ideas for Script Advancement
- Multi-Timeframe Dashboard: Extend the dynamic EMA concept to plot EMAs from three different timeframes simultaneously on the same chart using
request.security()with the non-repainting pattern, and usetimeframe.periodto auto-label each line. - Adaptive ATR-Based Stop Loss: Replace the fixed ATR multiplier with a value derived from
timeframe.multiplier, so that stop distances automatically widen on higher timeframes and tighten on lower ones, maintaining consistent risk-per-bar exposure across all chart resolutions.
Comments
Post a Comment