Indicator · Pine Script v6 · On the chart

Time Window Highlight

by piecioshka

Time Window Highlight is an intraday chart overlay that shades specific daily time windows with background boxes.

View on GitHub

GitHub stars2on the repository
Pine versionv6current
Lines of code238
Settings13inputs
LicenceMIT
Last update10 days ago18 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

238 lines of Pine Script v6 from indicators/overlays/time-window-highlight/time-window-highlight.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

time-window-highlight.pine
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Piotr Kowalski "piecioshka"
// Docs (EN): https://github.com/piecioshka/tradingview-pine-scripts/blob/main/indicators/overlays/time-window-highlight/time-window-highlight.md
// Docs (PL): https://github.com/piecioshka/tradingview-pine-scripts/blob/main/indicators/overlays/time-window-highlight/time-window-highlight.pl.md

//@version=6
// Highlights the same clock window on every day - a single shaded rectangle
// spanning the full height of the pane, from the bar that first reaches the
// start time to the last bar before the end time. Both times are given as
// HH:MM and are computed in a selectable time zone (Warsaw by default),
// because the chart's time axis usually shows the user's zone rather than the
// exchange's. A window whose start is later than its end wraps over midnight
// (e.g. 22:00-02:00), which covers overnight futures and the Asian session.
// The color tells the direction, measured on a lower timeframe so that the
// result does not change with the chart's own timeframe: green when the price
// has risen between the start of the window and the latest point inside it,
// red when it has fallen, and gray when it sits exactly at the opening
// level or the lower-timeframe data does not reach that far back. The
// rectangle is drawn as a box rather than bgcolor() so that it stays a single
// object per day, can be recolored on every bar, and can be stretched as more
// history loads. Works on intraday timeframes - on D and above every bar is a
// whole day, so the indicator only shows a hint.
indicator('Time Window Highlight', overlay = true, max_boxes_count = 500)

GRP_WINDOW = 'Window'
startTimeInput = input.string('17:30', 'Start time', group = GRP_WINDOW, tooltip = 'Start of the window as HH:MM, inclusive - the highlight begins on the first bar that has reached this time. A malformed value (wrong shape, hour outside 0-23, minute outside 0-59) disables the indicator.')
endTimeInput = input.string('18:00', 'End time', group = GRP_WINDOW, tooltip = 'End of the window as HH:MM, exclusive - the highlight ends on the last bar before this time. An end earlier than the start wraps the window over midnight, e.g. 22:00-02:00.')

GRP_GENERAL = 'General'
tzMode = input.string('Warsaw', 'Time zone', options = ['Warsaw', 'London', 'New York', 'Exchange', 'UTC', 'Custom'], group = GRP_GENERAL, tooltip = 'The zone the start and end times are computed in. Set it to the same zone the chart time axis shows, otherwise the highlight lands shifted against the axis.')
tzCustom = input.string('Europe/Warsaw', 'Custom time zone (IANA)', group = GRP_GENERAL, tooltip = 'Used only when "Custom" is selected. E.g. Europe/Berlin, America/Chicago, Asia/Tokyo.')

GRP_APPEARANCE = 'Appearance'
bgUpColor = input.color(color.new(#089981, 70), 'Up color', group = GRP_APPEARANCE, tooltip = 'Used when the price has risen inside the window - the latest price inside it is above the price at the start time.')
bgDownColor = input.color(color.new(#F23645, 70), 'Down color', group = GRP_APPEARANCE, tooltip = 'Used when the price has fallen inside the window - the latest price inside it is below the price at the start time.')
bgFlatColor = input.color(color.new(#787B86, 70), 'Unchanged color', group = GRP_APPEARANCE, tooltip = 'Used when the price has not moved inside the window, and when the direction cannot be established - the lower-timeframe data TradingView provides does not reach every bar of a long history, and older windows on large chart timeframes stay neutral.')
showBorder = input.bool(false, 'Show border', group = GRP_APPEARANCE, tooltip = 'Draws the outline of the rectangle. The left and right edges mark the exact bars the window starts and ends on.')
borderUpColor = input.color(color.new(#089981, 50), 'Border up color', group = GRP_APPEARANCE)
borderDownColor = input.color(color.new(#F23645, 50), 'Border down color', group = GRP_APPEARANCE)
borderFlatColor = input.color(color.new(#787B86, 50), 'Border unchanged color', group = GRP_APPEARANCE)
borderStyleName = input.string('Solid', 'Border style', options = ['Solid', 'Dashed', 'Dotted'], group = GRP_APPEARANCE)
borderWidth = input.int(1, 'Border width', minval = 1, maxval = 4, group = GRP_APPEARANCE)

tz = switch tzMode
    'Warsaw' => 'Europe/Warsaw'
    'London' => 'Europe/London'
    'New York' => 'America/New_York'
    'Exchange' => syminfo.timezone
    'UTC' => 'UTC'
    => tzCustom

borderStyle = switch borderStyleName
    'Solid' => line.style_solid
    'Dotted' => line.style_dotted
    => line.style_dashed

// "09:30" -> 570 minutes from midnight; returns na for a malformed value
minutesOf(string hhmm) =>
    parts = str.split(str.replace_all(hhmm, ' ', ''), ':')
    int result = na
    if array.size(parts) == 2
        h = str.tonumber(array.get(parts, 0))
        m = str.tonumber(array.get(parts, 1))
        if not na(h) and not na(m) and h >= 0 and h <= 23 and m >= 0 and m <= 59
            result := int(h) * 60 + int(m)
    result

startMinutes = minutesOf(startTimeInput)
endMinutes = minutesOf(endTimeInput)

nowMinutes = hour(time, tz) * 60 + minute(time, tz)

// The start is inclusive and the end exclusive, so a bar stamped exactly at
// the end time already falls outside. A start later than the end wraps over
// midnight, and then the window is the union of the two ends of the day
// instead of the span between them. Equal times would mean a zero-length
// window, which is never inside.
inWindow = na(startMinutes) or na(endMinutes) or startMinutes == endMinutes ? false : startMinutes < endMinutes ? nowMinutes >= startMinutes and nowMinutes < endMinutes : nowMinutes >= startMinutes or nowMinutes < endMinutes

// The direction is measured on a lower timeframe rather than on the chart's
// own bars, because the chart's bars do not have to line up with the window.
// On a 7-minute chart no bar opens exactly at 09:30, and on a 1-hour chart a
// single bar can swallow the whole window - taking the open of "the first bar
// inside the window" would then measure a different stretch of time on every
// timeframe. Measured on UKOIL for a 09:30-10:30 window that moved +0.95: the
// chart-bar method returned +0.94 on 7m and +0.19 on 1h, while the lower
// timeframe reference stays +0.95 everywhere.
//
// The source timeframe is picked rather than fixed at one minute, because
// request.security_lower_tf stops after 100,000 lower-timeframe bars. Asking
// for minutes on a 1-hour chart spends 60 of them per bar and runs out after
// 1,740 bars - measured on UKOIL, 19,428 of 21,168 bars came back with empty
// arrays, which left almost the whole history without a direction. A coarser
// source stretches the same budget: on a 1-hour chart a 30-minute source
// reaches ~50,000 bars instead of 1,740.
//
// The step must divide both ends of the window, otherwise its bars stop
// landing on the window's edges and the original timeframe dependence comes
// back. A window on a plain half hour keeps a coarse step available; one on
// an odd minute (09:33) leaves only '1'.
LTF_LIMIT = 100000
// Enough to cover any realistic history; beyond this the step cannot grow
// without breaking the window's edges anyway.
LTF_TARGET_BARS = 20000
chartMinutes = timeframe.in_seconds() / 60

// Candidates are limited to divisors of an hour, so they always tile the clock
// the same way on every day.
//
// Divisibility is the hard requirement: a step that misses an edge of the
// window reintroduces the timeframe dependence this whole mechanism exists to
// remove. A step coarser than the chart is not a lower timeframe at all.
stepAllowed(int step) =>
    step < chartMinutes and (na(startMinutes) or startMinutes % step == 0) and (na(endMinutes) or endMinutes % step == 0)

// Coverage is the soft one - it decides which of the allowed steps to take.
stepCovers(int step) =>
    LTF_LIMIT / math.max(1, math.floor(chartMinutes / step)) >= LTF_TARGET_BARS

stepFits(int step) =>
    stepAllowed(step) and stepCovers(step)

// Coarsest first, because the finest step that divides the window is '1' on
// every chart - exactly the case that runs out of budget. If no step reaches
// the coverage target (very large chart timeframes), fall back to the coarsest
// allowed one anyway: it still divides the window and still stretches the
// budget further than '1' would.
ltfStep = stepFits(60) ? 60 : stepFits(30) ? 30 : stepFits(15) ? 15 : stepFits(10) ? 10 : stepFits(5) ? 5 : stepFits(3) ? 3 : stepFits(2) ? 2 : stepAllowed(60) ? 60 : stepAllowed(30) ? 30 : stepAllowed(15) ? 15 : stepAllowed(10) ? 10 : stepAllowed(5) ? 5 : stepAllowed(3) ? 3 : stepAllowed(2) ? 2 : 1
ltfSpec = str.tostring(ltfStep)

ltfTime = request.security_lower_tf(syminfo.tickerid, ltfSpec, time)
ltfOpen = request.security_lower_tf(syminfo.tickerid, ltfSpec, open)
ltfClose = request.security_lower_tf(syminfo.tickerid, ltfSpec, close)

minutesOfTime(int t) =>
    hour(t, tz) * 60 + minute(t, tz)

// Whether a minute belongs to the window, using the same inclusive-start,
// exclusive-end rule (and the same midnight wrap) as the highlight itself.
minuteInWindow(int m) =>
    na(startMinutes) or na(endMinutes) or startMinutes == endMinutes ? false : startMinutes < endMinutes ? m >= startMinutes and m < endMinutes : m >= startMinutes or m < endMinutes

// The reference is the open of the window's opening minute and the comparison
// value is the close of the latest minute seen so far inside it. Both are kept
// across bars, because a chart bar only carries the minutes it contains - the
// opening minute and the current one rarely sit in the same bar.
var float windowOpen = na
var float windowLast = na
if array.size(ltfTime) > 0
    for i = 0 to array.size(ltfTime) - 1
        m = minutesOfTime(array.get(ltfTime, i))
        // The opening minute both sets the reference and discards the previous
        // window's value, so a bar holding the end of one window and the start
        // of the next cannot carry a stale reference over.
        if m == startMinutes
            windowOpen := array.get(ltfOpen, i)
            windowLast := array.get(ltfClose, i)
        else if minuteInWindow(m)
            windowLast := array.get(ltfClose, i)

// Vertical bounds of the highlight. TradingView silently skips boxes whose
// top/bottom lie extremely far from the price scale (measured on an instrument
// near 85: bounds of +-1e8 still draw, +-1e9 do not), so absurd constants like
// 1e17 produce boxes that exist but never render. Instead the bounds follow
// the price range of the loaded data - 100x that range above and below covers
// the full height of the pane at any realistic zoom while staying far inside
// the renderer's limits.
BG_PAD = 100.0
var float dataHigh = na
var float dataLow = na
if not na(high) and not na(low)
    dataHigh := na(dataHigh) ? high : math.max(dataHigh, high)
    dataLow := na(dataLow) ? low : math.min(dataLow, low)
bgPad = math.max(dataHigh - dataLow, syminfo.mintick) * BG_PAD
bgTop = dataHigh + bgPad
bgBottom = dataLow - bgPad

// One box per window instead of bgcolor(), which paints a single bar at a
// time and cannot be revisited later - a box spans the whole window and its
// bounds can be corrected as more history loads.
//
// The horizontal bounds are timestamps (xloc.bar_time), not bar indices,
// because a window does not have to contain more than one bar. A 09:30-10:30
// window holds two bars on a 30-minute chart but only one on a 1-hour chart,
// and a box from that bar's index to itself has zero width - it exists in the
// object tree and never appears on screen, which is exactly what happened on
// 1h before. Timestamps give the box the window's real duration, so it stays
// an hour wide on every timeframe.
MS_PER_MINUTE = 60000
var box currentBox = na

// Midnight of the current bar's day, in the selected time zone - the anchor
// the window's own timestamps are measured from.
dayStart = timestamp(tz, year(time, tz), month(time, tz), dayofmonth(time, tz), 0, 0)
// A window that wraps past midnight belongs to the day it started on, so a bar
// sitting in its tail (before the end time) anchors to the previous midnight.
windowDayStart = not na(startMinutes) and not na(endMinutes) and startMinutes > endMinutes and nowMinutes < endMinutes ? dayStart - 24 * 60 * MS_PER_MINUTE : dayStart
windowStartTime = windowDayStart + startMinutes * MS_PER_MINUTE
// The end is exclusive, so the box reaches the end time without covering it.
windowEndTime = windowStartTime + (startMinutes < endMinutes ? endMinutes - startMinutes : 24 * 60 - startMinutes + endMinutes) * MS_PER_MINUTE

// Three states, not two: a value exactly at the opening level is neither a
// rise nor a fall and gets its own neutral color instead of being folded into
// one of them. An unknown direction (no 1-minute data yet) is neutral too.
directionColor(float reference, float value, color up, color down, color flat) =>
    na(reference) or na(value) ? flat : value > reference ? up : value < reference ? down : flat

if timeframe.isintraday
    if inWindow
        if na(currentBox)
            currentBox := box.new(windowStartTime, bgTop, windowEndTime, bgBottom, xloc = xloc.bar_time, border_color = color.new(color.black, 100), border_width = showBorder ? borderWidth : 1, border_style = borderStyle, bgcolor = bgFlatColor)
        else
            box.set_right(currentBox, windowEndTime)
        // The direction is re-evaluated on every bar of the window, so an
        // ongoing window changes color as the price crosses its opening level;
        // a closed one keeps the state of its last bar.
        box.set_bgcolor(currentBox, directionColor(windowOpen, windowLast, bgUpColor, bgDownColor, bgFlatColor))
        box.set_border_color(currentBox, showBorder ? directionColor(windowOpen, windowLast, borderUpColor, borderDownColor, borderFlatColor) : color.new(color.black, 100))
    else
        // Leaving the window closes the box for good; the next entry starts a
        // new one.
        currentBox := na
        windowOpen := na
        windowLast := na

    // Boxes of earlier windows were created while less data was loaded, so
    // their bounds are narrower - on the last bar bring every box to the final
    // bounds. Runs only when the bounds actually change.
    var float sweptTop = na
    if barstate.islast and (na(sweptTop) or bgTop != sweptTop)
        sweptTop := bgTop
        for b in box.all
            box.set_top(b, bgTop)
            box.set_bottom(b, bgBottom)
else
    if barstate.islast
        var table hint = table.new(position.top_right, 1, 1)
        table.cell(hint, 0, 0, 'Time Window Highlight: the indicator works on intraday timeframes', text_color = color.gray, text_size = size.small)

Overview

Time Window Highlight measures price performance from the start of the defined time window to the latest point within it, coloring the background green when price rises, red when it falls, and gray when unchanged. The script supports custom start and end times (including overnight windows that wrap over midnight), pre-set time zones, and custom IANA time zones. Users can customize background opacity, border visibility, colors, and line styles.

Details

Published by

piecioshka on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Sessions & time

Markets

Any symbol

Timeframes

M1-M5, M15-H1

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

pinescriptpinescript-indicatorspinescript-strategiestradingtradingview

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Start timestring17:30startTimeInput
End timestring18:00endTimeInput
Time zonestringWarsawtzMode
Custom time zone (IANA)stringEurope/WarsawtzCustom
Up colorcolorcolor.new(#089981, 70)bgUpColor
Down colorcolorcolor.new(#F23645, 70)bgDownColor
Unchanged colorcolorcolor.new(#787B86, 70)bgFlatColor
Show borderboolfalseshowBorder
Border up colorcolorcolor.new(#089981, 50)borderUpColor
Border down colorcolorcolor.new(#F23645, 50)borderDownColor
Border unchanged colorcolorcolor.new(#787B86, 50)borderFlatColor
Border stylestringSolidborderStyleName
Show all 13 settings
SettingTypeDefaultVariable
Border widthint1borderWidth
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

948 stars

This indicator counts down to a fixed Litecoin halving timestamp in August 2019, using Greenwich Mean Time. It subtracts the current time from that stamp and formats the remaining days, hours, minutes, and seconds. The day, hour, and minute of the target are u

Indicator v4 Sessions & time
Updated 10 days ago · GPL-3.0 GitHub repository of Litecoin Halving UTC Countdown
575 stars

This indicator builds Market Profile Time Price Opportunity (TPO) structures across defined session timeframes directly on the main price chart. It visualizes the Point of Control (POC) and Value Area boundaries using dynamic box drawings. An integrated multi-

Indicator v5 Support & resistanceScreeners & dashboards
Updated 1 year ago · MPL-2.0 GitHub repository of Market Profile with Screener

This indicator identifies and plots key options expiration dates on the price chart. It calculates monthly OpEx (the third Friday of each month), quarterly OpEx (March, June, September, December), and corresponding VIX expiration dates, drawing vertical lines

Indicator v6 Sessions & time
Updated 2 months ago · MIT GitHub repository of Expiration Highlights: Opex, Quarterly Opex & VIX with Vanna/Charm
257 stars

This indicator plots auto trade signals for E-mini S&P 500 futures on the price chart. It computes an EMA200 filter along with day ETH RTH session levels and percentage-based entry zones. Signals trigger CALL or PUT cases when price crosses defined thresholds

Indicator v6 TrendSupport & resistance
Updated 6 weeks ago · None stated GitHub repository of Auto Trade Futures ES
219 stars

This indicator plots reference levels for previous daily high and low prices directly on the price chart. It requests daily timeframe data using the security function and plots the offset values as continuous lines, displaying resistance in red and support in

Indicator v4 Support & resistanceSessions & time
Updated 5 years ago · None stated GitHub repository of 昨日高低點標示

Golden Bullet Strategy

by TradersPost
218 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers