Indicator · Pine Script v5 · Own pane

Tops & Bottoms - Day of Week

by sbtnc

Reports on which weekday the high and low of each trading week tend to occur, shown as columns in a separate pane.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code420
Settings7inputs
LicenceMPL-2.0
Last update9 months ago28 Jan 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

420 lines of Pine Script v5 from Tops & Bottoms - Day of Week Report.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.

Tops & Bottoms - Day of Week Report.pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © sbtnc
// Created: 2023-11-29
// Last modified: 2023-12-09
// Version 1.0


//@version=5
indicator("Tops & Bottoms - Day of Week", format = format.percent, max_labels_count = 21)


//--------------------------------------------------------------------
//#region                      Constants
//--------------------------------------------------------------------

int             COLUMN_WIDTH                = 4
int             COLUMN_GAP                  = 4
color           COLUMN_BORDERCOLOR          = color.new(chart.fg_color, 80)
color           COLUMN_TOP_MAX_COLOR        = color.green
color           COLUMN_TOP_MIN_COLOR        = #0c3299
color           COLUMN_BOTTOM_MAX_COLOR     = color.red
color           COLUMN_BOTTOM_MIN_COLOR     = #311b92
color           CATEGORY_LABEL_BGCOLOR      = color.new(chart.bg_color, 50)
color           CATEGORY_LABEL_TEXTCOLOR    = chart.fg_color
array<string>   TIMEFRAMES                  = array.from("D", "1440")
array<int>      DAYSOFWEEK                  = array.from(dayofweek.sunday, dayofweek.monday, dayofweek.tuesday, dayofweek.wednesday, dayofweek.thursday, dayofweek.friday, dayofweek.saturday)

//#endregion


//--------------------------------------------------------------------
//#region                        Inputs
//--------------------------------------------------------------------

intervalTooltip     = "By default, the indicator identifies the top and bottom over the symbol's trading week. " +
                      "You can specify your custom interval."
noticeTitle         = "Navigate to the 1-day timeframe (or 24-hour for including the extended/electronic trading hours) " +
                      "for the indicator to analyze the bars and collect tops and bottoms."

customIntervalInput = input.bool    (false, "Custom Interval", inline = "Interval")
intervalStartInput  = input.string  ("Monday", "", ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], inline = "Interval")
intervalEndInput    = input.string  ("Sunday", " — ", ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], tooltip = intervalTooltip, inline = "Interval")
intervalTypeInput   = input.string  ("All", "Interval", ["All", "Up", "Down"], group = "Filters") 
startDateInput      = input.time    (timestamp("01 Jan 1975"), "From", group = "Filters")
endDateInput        = input.time    (timestamp("31 Dec 2050"), "To", group = "Filters")
noticeInput         = input.bool    (true, noticeTitle, group = "How To Use?", confirm = true, display = display.none)

//#endregion


//--------------------------------------------------------------------
//#region                         Types 
//--------------------------------------------------------------------

type Marker
    float price = na
    int   time  = na

//#endregion


//--------------------------------------------------------------------
//#region                 Variables declarations
//--------------------------------------------------------------------

var topsByDayOfWeekMap       = map.new<int, int>()
var bottomsByDayOfWeekMap    = map.new<int, int>()
var openTimesByDayOfWeekMap  = map.new<int, int>()
var closeTimesByDayOfWeekMap = map.new<int, int>() 
var top                      = Marker.new()
var bottom                   = Marker.new()

//#endregion


//--------------------------------------------------------------------
//#region                   Functions & methods
//--------------------------------------------------------------------

// @function Get the day of the week index from a given UNIX time.
// @returns  (int) Day of the week
getDayOfWeek(int barTime) =>
    dayofweek(barTime, "UTC+0")


// @function Get the day of the week short string from a given index.
// @returns  (string) Day of the week
getDayOfWeekShortString(int dayOfWeek) =>
    switch dayOfWeek
        dayofweek.sunday    => "Sun"
        dayofweek.monday    => "Mon"
        dayofweek.tuesday   => "Tue"
        dayofweek.wednesday => "Wed"
        dayofweek.thursday  => "Thu"
        dayofweek.friday    => "Fri"
        dayofweek.saturday  => "Sat"


// @function Get the day of the week long string from a given index.
// @returns  (string) Day of the week
getDayOfWeekLongString(int dayOfWeek) =>
    switch dayOfWeek
        dayofweek.sunday    => "Sunday"
        dayofweek.monday    => "Monday"
        dayofweek.tuesday   => "Tuesday"
        dayofweek.wednesday => "Wednesday"
        dayofweek.thursday  => "Thursday"
        dayofweek.friday    => "Friday"
        dayofweek.saturday  => "Saturday"


// @function Check if the market has an overnight session based on its opening and closing times.
// @returns  bool
hasOvernightSession(int openTime, int closeTime) =>
    dayofweek(openTime) != dayofweek(closeTime)


// @function Get the legend's tooltip text.
// @returns  string 
getSessionTooltip(int openTime, int closeTime) =>
    string _dayOfWeekAtOpen  = getDayOfWeekLongString(dayofweek(openTime))
    string _dayOfWeekAtClose = getDayOfWeekLongString(dayofweek(closeTime))
    string _timeAtOpen       = str.format_time(openTime, "HH:mm", syminfo.timezone)
    string _timeAtClose      = str.format_time(closeTime, "HH:mm", syminfo.timezone)

    switch hasOvernightSession(openTime, closeTime)
        true    => str.format("{0} {1} to {2} {3} (Exchange time)", _dayOfWeekAtOpen, _timeAtOpen, _timeAtClose, _dayOfWeekAtClose)
        false   => str.format("{0} {1} to {2} (Exchange time)", _dayOfWeekAtOpen, _timeAtOpen, _timeAtClose)


// @function Get the color based on a gradient between the minimum and maximum values.
// @returns  color 
getGradientColor(float value, float minValue, float maxValue, color minColor, color maxColor) =>
    // When `minValue` and `maxValue` are identical (on the first collected values), return the mid gradient color instead of no color.
    minValue != maxValue ? color.from_gradient(value, minValue, maxValue, minColor, maxColor) : color.from_gradient(0.5, 0, 1, minColor, maxColor)
    

// @function Draw the columns for a given day of the week.
// @returns  void
drawDayOfWeekColumns(int key) =>
    var _upperColumnBox = box.new(na, na, na, na, COLUMN_BORDERCOLOR)
    var _lowerColumnBox = box.new(na, na, na, na, COLUMN_BORDERCOLOR)
    var _upperDataLabel = label.new(na, na, color = color(na), style = label.style_label_down)
    var _lowerDataLabel = label.new(na, na, color = color(na), style = label.style_label_up)
    var _categoryLabel  = label.new(na, na, color = CATEGORY_LABEL_BGCOLOR, style = label.style_label_center, textcolor = CATEGORY_LABEL_TEXTCOLOR)
    var _gridMultiplier = COLUMN_WIDTH + COLUMN_GAP 
    
    if barstate.islast
        _topsArray      = topsByDayOfWeekMap.values()
        _bottomsArray   = bottomsByDayOfWeekMap.values()

        // Sum the number of data sampled.
        _dataSum        = _topsArray.sum()


        // Calculate the shares of occurrence of tops and bottoms.

        _top            = topsByDayOfWeekMap.get(key)
        _bottom         = bottomsByDayOfWeekMap.get(key)
        _topY           = nz(_top    / _dataSum * 100)
        _bottomY        = nz(_bottom / _dataSum * 100)


        // Get the coordinates for plotting the columns chart (on the left or right side of the last bar, given enough space).

        _columnsCount   = 7
        _hasSpace       = bar_index - _columnsCount * _gridMultiplier > 0
        _x1             = _hasSpace ? bar_index + (key - _columnsCount - 1) * _gridMultiplier : bar_index + key * _gridMultiplier
        _x2             = _x1 + COLUMN_WIDTH
        _center         = _x1 + COLUMN_WIDTH / 2


        // Get the coloring of the columns and data labels.

        _topsMin        = _topsArray.min()      / _dataSum * 100
        _topsMax        = _topsArray.max()      / _dataSum * 100
        _bottomsMin     = _bottomsArray.min()   / _dataSum * 100
        _bottomsMax     = _bottomsArray.max()   / _dataSum * 100
        _topColor       = getGradientColor(_topY, _topsMin, _topsMax, COLUMN_TOP_MIN_COLOR, COLUMN_TOP_MAX_COLOR)
        _bottomColor    = getGradientColor(_bottomY, _bottomsMin, _bottomsMax, COLUMN_BOTTOM_MIN_COLOR, COLUMN_BOTTOM_MAX_COLOR)


        // Plot the upper and lower columns for tops and bottoms.

        _upperColumnBox.set_lefttop     (_x1, _topY)
        _upperColumnBox.set_rightbottom (_x2, 0)
        _upperColumnBox.set_bgcolor     (_topColor)

        _lowerColumnBox.set_lefttop     (_x1, 0)
        _lowerColumnBox.set_rightbottom (_x2, -_bottomY)
        _lowerColumnBox.set_bgcolor     (_bottomColor)


        // Plot the X-axis category label.

        _openTime                   = openTimesByDayOfWeekMap.get(key)
        _closeTime                  = closeTimesByDayOfWeekMap.get(key)
        _isCategoryDefined          = not (na(_openTime) or na(_closeTime))

        _categoryLabel.set_xy       (_center, 0)
        _categoryLabel.set_text     (getDayOfWeekShortString(key))
        _categoryLabel.set_tooltip  (_isCategoryDefined ? getSessionTooltip(_openTime, _closeTime) : "Not Collected")


        // Plot the data labels for tops and bottoms.

        if not na(_top)
            _upperDataLabel.set_xy          (_center, _topY)
            _upperDataLabel.set_text        (str.tostring(math.round(_topY)))
            _upperDataLabel.set_textcolor   (_topColor)
            _upperDataLabel.set_tooltip     (str.format("{0} based on {1} sampled weeks", str.tostring(_topY, format.percent), _dataSum))

        if not na(_bottom)
            _lowerDataLabel.set_xy          (_center, -_bottomY)
            _lowerDataLabel.set_text        (str.tostring(math.round(_bottomY)))
            _lowerDataLabel.set_textcolor   (_bottomColor)
            _lowerDataLabel.set_tooltip     (str.format("{0} based on {1} sampled weeks", str.tostring(_bottomY, format.percent), _dataSum))


// @function Check if the week/custom interval matches the user-selected interval type.
// @returns  bool 
isValidIntervalType(start, end, isInRange) =>
    var float _open = na
    bool _isValid   = na

    // The first bar of the user-selected time range can occur after the interval start.
    if ta.change(isInRange) and isInRange
        _open := open

    if start
        _open := open

    if end
        _isValid := switch intervalTypeInput
            "All"   => not na(_open[1])
            "Up"    => 
                close[1] > _open[1]
            "Down"  => 
                close[1] < _open[1]

    _isValid


// @function Check if the current bar time is in the user-selected time range.
// @returns  bool
isInTimeRange() =>
    time >= startDateInput and time <= endDateInput


// @function Get the day of the week index from a given string.
// @returns  (int) Day of the week
getDayOfWeek(string dayOfWeek) =>
    switch dayOfWeek
        "Sunday"    => dayofweek.sunday
        "Monday"    => dayofweek.monday
        "Tuesday"   => dayofweek.tuesday
        "Wednesday" => dayofweek.wednesday
        "Thursday"  => dayofweek.thursday
        "Friday"    => dayofweek.friday
        "Saturday"  => dayofweek.saturday


// @function Get the end day of the week index from the user-selected interval (shifted by one to be inclusive).
// @returns  (int) Day of the week
getEndDayOfWeek() =>
    switch intervalEndInput
        "Sunday"    => dayofweek.monday
        "Monday"    => dayofweek.tuesday
        "Tuesday"   => dayofweek.wednesday
        "Wednesday" => dayofweek.thursday
        "Thursday"  => dayofweek.friday
        "Friday"    => dayofweek.saturday
        "Saturday"  => dayofweek.sunday


// @function Construct a suite of days of the week for a given index.
// @returns  array<int>
method getSuite(array<int> this, int dayOfWeek) =>
    _src = this.copy()
    _index = _src.indexof(dayOfWeek)
    _part1 = _src.slice(_index, 7)
    _part2 = _src.slice(0, _index)
    _part1.concat(_part2)


// @function Detect the changes in the week/custom interval.
// @returns  ([bool, bool, bool]) Start, end, time is in interval
intervalChange() =>
    if customIntervalInput
        _dayOfWeek      = dayofweek(time_tradingday, "UTC+0")


        // Construct the lists of following days of the week.

        _suiteFromStart = DAYSOFWEEK.getSuite(getDayOfWeek(intervalStartInput))
        _suiteFromEnd   = DAYSOFWEEK.getSuite(getEndDayOfWeek())
        

        // Determine if the current day of the week is in the interval.

        _dayOfWeekList  = _suiteFromStart.copy().slice(0, _suiteFromStart.indexof(getDayOfWeek(intervalEndInput)) +1)
        _isInInterval   = _dayOfWeekList.includes(_dayOfWeek)
        

        // Calculate the next start and end times of the interval.

        _barsToStart = 7 - _suiteFromStart.indexof(_dayOfWeek)
        _barsToEnd = 7 - _suiteFromEnd.indexof(_dayOfWeek)
        _startTime = timestamp(year, month, dayofmonth + _barsToStart, hour, minute)
        _endTime = timestamp(year, month, dayofmonth + _barsToEnd, hour, minute)


        // Determine if the day is the first day of the interval / after the interval.
        // Which are not necessarily on the interval's start and end day of week (e.g., on EURUSD, Monday is the first trading day in the interval [Saturday - Wednesday]).

        _start = ta.change(_startTime) > 0 and _isInInterval
        _end = ta.change(_endTime) > 0 and _isInInterval[1]

        [_start, _end, _isInInterval]
    else

        // Start and end on the week change.
        _weekChange = timeframe.change("W")

        [_weekChange, _weekChange, true]

//#endregion


//--------------------------------------------------------------------
//#region                         Logic
//--------------------------------------------------------------------

if TIMEFRAMES.indexof(timeframe.period) == -1
    runtime.error("The report can not compute data on the chart's timeframe. Please navigate to the 1-day or 24-hour timeframe.")

//@variable Is true when the current bar is inside the time range filter.
isInRange = isInTimeRange()

// Interval variables from the week/custom interval.
[intervalStart, intervalEnd, isInInterval] = intervalChange()

// @variable Is true when the week/custom interval closes per the interval type filter.
isValidType = isValidIntervalType(intervalStart, intervalEnd, isInRange)


// Track the top and bottom of the week/custom interval.

if isInRange
    
    // Reset the top and bottom on the first bar of a new week/custom interval.
    
    if intervalStart
        top.price       := na
        top.time        := na
        bottom.price    := na
        bottom.time     := na


    // Track the top and bottom.

    if na(top.price) or high > top.price
        top.price       := high
        top.time        := time_tradingday
    
    if na(bottom.price) or low < bottom.price
        bottom.price    := low
        bottom.time     := time_tradingday


// Collect the top, bottom, and time data.

if isInRange and barstate.isconfirmed
    // @variable Is true on the first bar of the time range.
    bool _isFirstBar = na(top.time[1]) or na(bottom.time[1])
    

    // Collect the top and bottom on a week/custom interval change if it conforms to the user-selected type.

    if isValidType and not _isFirstBar
        int _topWeekday     = getDayOfWeek(top.time[1])
        int _bottomWeekday  = getDayOfWeek(bottom.time[1])

        int _topCount       = nz(topsByDayOfWeekMap.get(_topWeekday)) +1
        int _bottomCount    = nz(bottomsByDayOfWeekMap.get(_bottomWeekday)) +1

        topsByDayOfWeekMap.put(_topWeekday, _topCount)
        bottomsByDayOfWeekMap.put(_bottomWeekday, _bottomCount)

        log.info(
             "\n▲ Top on {0} ({1}/{4})\n▼ Bottom on {2} ({3}/{4})",
             getDayOfWeekLongString(_topWeekday), _topCount, getDayOfWeekLongString(_bottomWeekday), _bottomCount, topsByDayOfWeekMap.values().sum()
             )


    // Collect the opening and closing times.
    // The times are be displayed in the tooltips of category labels.

    int _weekday = getDayOfWeek(time_tradingday)

    if isInInterval
        openTimesByDayOfWeekMap.put(_weekday, time)
        closeTimesByDayOfWeekMap.put(_weekday, time_close)

//#endregion


//--------------------------------------------------------------------
//#region                        Visuals
//--------------------------------------------------------------------

drawDayOfWeekColumns(dayofweek.sunday)
drawDayOfWeekColumns(dayofweek.monday)
drawDayOfWeekColumns(dayofweek.tuesday)
drawDayOfWeekColumns(dayofweek.wednesday)
drawDayOfWeekColumns(dayofweek.thursday)
drawDayOfWeekColumns(dayofweek.friday)
drawDayOfWeekColumns(dayofweek.saturday)

//#endregion

Overview

Tops & Bottoms - Day of Week collects data from daily bars, so it must be run on the 1-day chart. Settings allow a custom interval of days, a filter for up or down weeks, and a date range. The result is a historical count for the loaded data, not a forecast.

Details

Published by

sbtnc on GitHub

Draws

Separate pane below the chart

Markets

Any symbol

Timeframes

H4 and above

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

pine-scriptpinescriptpinescript-indicators

Settings

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

SettingTypeDefaultVariable
Custom IntervalboolfalsecustomIntervalInput
intervalStartInputstringMondayintervalStartInput
— stringSundayintervalEndInput
IntervalstringAllintervalTypeInput
Fromtimetimestamp("01 Jan 1975")startDateInput
Totimetimestamp("31 Dec 2050")endDateInput
noticeInputbooltruenoticeInput
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

This indicator plots a Supertrend-based trend-following setup in a separate pane and skips conditions it treats as a sideways market. A volatility range is built from an ATR value, with RMA, SMA, EMA, or WMA smoothing, or from a fixed point distance. Optional

Indicator v5 TrendVolatility & bands
Updated 1 year ago · MPL-2.0 GitHub repository of Trend Following Setup - Sideways Market Skipper
550 stars

Range Volume Change compares volume inside a chosen session window with the same window on the previous day and plots that change as a percentage. It accumulates volume during the window and, when enabled, volume outside it, drawing growing columns in a separa

Indicator v5 VolumeSessions & time Repaints
Updated 10 months ago · None stated GitHub repository of Range Volume Change
257 stars

Donald SPX - Options Trading is an indicator designed to assist SPX index option traders by tracking session benchmarks and multi-timeframe moving average data. It calculates a 200-period weighted moving average (WMA) and requests multi-timeframe bar data from

Indicator v5 Moving averagesScreeners & dashboards Repaints
Updated 7 weeks ago · None stated GitHub repository of Donald SPX - Options Trading

Bar date/time

by be-thomas
65 stars

This indicator plots each bar's opening time as a numeric series in a separate pane. It uses Pine Script's built-in time value and draws one plot, not an overlay on price. There are no inputs, so the line follows the chart's bar timestamps without filters or o

Indicator v4 Sessions & time
Updated 6 days ago · GPL-3.0 GitHub repository of Bar date/time
46 stars

This indicator summarizes higher-timeframe candle range statistics by time of day and shows them in a table in a separate pane. It groups candles into slots for 5-minute, 15-minute, 1-hour, 4-hour, 8-hour, or 12-hour periods over a chosen lookback in days, and

Indicator v5 Screeners & dashboardsSessions & time Repaints
Updated 8 weeks ago · None stated GitHub repository of HTF Candle Stats by Time of Day
10 stars

This indicator plots the active hours of major global trading sessions including Tokyo, Berlin, London, Sydney, and New York. It dynamically adjusts session times based on whether the instrument is classified as a traditional market or crypto. The script uses

Indicator v4 Sessions & time
Updated 5 years ago · None stated GitHub repository of My Trading Sessions

More Sessions & time scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers