Indicator · Pine Script v6 · On the chart

Peak Trading Activity Graphs [LuxAlgo]

by MeridianAlgo· code credited to LuxAlgo

Peak Trading Activity Graphs displays historical market activity across cyclical timeframes directly on the main chart using an on-screen dashboard table.

View on GitHub

GitHub stars9on the repository
Pine versionv6current
Lines of code352
Settings11inputs
LicenceMIT
Last update4 months ago29 Jun 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

352 lines of Pine Script v6 from examples/Peak_Trading_Activity_Graphs_LuxAlgo.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

Peak_Trading_Activity_Graphs_LuxAlgo.pine
// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © LuxAlgo

//@version=6
indicator('Peak Trading Activity Graphs [LuxAlgo]','LuxAlgo - PTAG', overlay = true)
//---------------------------------------------------------------------------------------------------------------------}
//CONSTANTS & STRINGS & INPUTS
//---------------------------------------------------------------------------------------------------------------------{
GREEN                   = #089981
RED                     = #F23645
YELLOW                  = #F2E805
DATA                    = #DBDBDB
BACKGROUND              = #161616
BORDERS                 = #2E2E2E
ORANGE                  = #FF5D00
ORANGE_LIGHT            = #FEF1E8

COLD_VIRIDIS            = #400A53
LUKEWARM_VIRIDIS        = #408E8B
HOT_VIRIDIS             = #F8E650

COLD_PLASMA             = #110A81
LUKEWARM_PLASMA         = #B8487D
HOT_PLASMA              = #F1F455

COLD_MAGMA              = #000005
LUKEWARM_MAGMA          = #9D3B7A
HOT_MAGMA               = #FCF7BE

VIRIDIS                 = 'Viridis'
PLASMA                  = 'Plasma'
MAGMA                   = 'Magma'
CUSTOM                  = 'Custom'

TOP_RIGHT               = 'Top Right'
BOTTOM_RIGHT            = 'Bottom Right'
BOTTOM_LEFT             = 'Bottom Left'

TINY                    = 'Tiny'
SMALL                   = 'Small'
NORMAL                  = 'Normal'
LARGE                   = 'Large'
HUGE                    = 'Huge'

VOLUME                  = 'Volume'
VOLATILITY              = 'Volatility'

DAYOFWEEK               = 'Days of Week'
DAYOFMONTH              = 'Days of Month'
MONTH                   = 'Months'
HOURS                   = 'Hours'

DASHBOARD_GROUP         = 'Graph'
STYLE_GROUP             = 'Style'

deltaTooltip            = 'Display the difference between the medians as a percentage. The smaller median is 0, and the larger median is 100. Enabling this feature highlights the differences between values.'
dashboardPositionTooltip= 'Select the graph location.'
dashboardSizeTooltip    = 'Select the graph text size.'
dashboardWidthTooltip   = 'Select the graph width.'
dashboardHeightTooltip  = 'Select the graph height.'

baseDataInput           = input.string( VOLUME,     'Data',     options = [VOLATILITY,VOLUME])
periodInput             = input.string( DAYOFMONTH, 'Period',   options = [MONTH,DAYOFMONTH,DAYOFWEEK,HOURS])
deltaInput              = input.bool(   false,      'Display Delta Between Medians', tooltip = deltaTooltip)

dashboardPositionInput  = input.string( TOP_RIGHT,  'Position', group = DASHBOARD_GROUP, tooltip = dashboardPositionTooltip ,   options = [TOP_RIGHT,BOTTOM_RIGHT,BOTTOM_LEFT])
dashboardSizeInput      = input.string( SMALL,      'Size',     group = DASHBOARD_GROUP, tooltip = dashboardSizeTooltip,        options = [TINY,SMALL,NORMAL,LARGE,HUGE])
dashboardWidthInput     = input.float(  1.0,        'Width',    group = DASHBOARD_GROUP, tooltip = dashboardWidthTooltip ,      minval = 1, step = 0.25)
dashboardHeightInput    = input.float(  1.0,        'Height',   group = DASHBOARD_GROUP, tooltip = dashboardHeightTooltip,      minval = 1, step = 0.25)

colormapInput           = input.string( VIRIDIS,    'Colors',           group = STYLE_GROUP,    options = [VIRIDIS,PLASMA,MAGMA,CUSTOM])
coldColorInput          = input.color(  RED,        'Custom Cold',      group = STYLE_GROUP)
lukewarmColorInput      = input.color(  YELLOW,     'Custom Lukewarm',  group = STYLE_GROUP)
hotColorInput           = input.color(  GREEN,      'Custom Hot',       group = STYLE_GROUP)

//---------------------------------------------------------------------------------------------------------------------}
//DATA STRUCTURES & VARIABLES
//---------------------------------------------------------------------------------------------------------------------{
type data
    array<float> values
    string tag = ''

var array<data> fullMonth = array.from(data.new(array.new<float>()),
     data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),
     data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),
     data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),
     data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),
     data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),
     data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()),data.new(array.new<float>()))

var array<data> fullWeek = syminfo.type == 'crypto' ? array.from(
     data.new(array.new<float>(),'SUN'),
     data.new(array.new<float>(),'MON'),
     data.new(array.new<float>(),'TUE'),
     data.new(array.new<float>(),'WED'),
     data.new(array.new<float>(),'THU'),
     data.new(array.new<float>(),'FRI'),
     data.new(array.new<float>(),'SAT')) : array.from(
     data.new(array.new<float>(),'MON'),
     data.new(array.new<float>(),'TUE'),
     data.new(array.new<float>(),'WED'),
     data.new(array.new<float>(),'THU'),
     data.new(array.new<float>(),'FRI'))

var array<data> fullYear = array.from(
     data.new(array.new<float>(),'JAN'),
     data.new(array.new<float>(),'FEB'),
     data.new(array.new<float>(),'MAR'),
     data.new(array.new<float>(),'APR'),
     data.new(array.new<float>(),'MAY'),
     data.new(array.new<float>(),'JUN'),
     data.new(array.new<float>(),'JUL'),
     data.new(array.new<float>(),'AUG'),
     data.new(array.new<float>(),'SEP'),
     data.new(array.new<float>(),'OCT'),
     data.new(array.new<float>(),'NOV'),
     data.new(array.new<float>(),'DEC'))

var array<data> fullDay = array.from(
     data.new(array.new<float>(),'0'),
     data.new(array.new<float>(),'1'),
     data.new(array.new<float>(),'2'),
     data.new(array.new<float>(),'3'),
     data.new(array.new<float>(),'4'),
     data.new(array.new<float>(),'5'),
     data.new(array.new<float>(),'6'),
     data.new(array.new<float>(),'7'),
     data.new(array.new<float>(),'8'),
     data.new(array.new<float>(),'9'),
     data.new(array.new<float>(),'10'),
     data.new(array.new<float>(),'11'),
     data.new(array.new<float>(),'12'),
     data.new(array.new<float>(),'13'),
     data.new(array.new<float>(),'14'),
     data.new(array.new<float>(),'15'),
     data.new(array.new<float>(),'16'),
     data.new(array.new<float>(),'17'),
     data.new(array.new<float>(),'18'),
     data.new(array.new<float>(),'19'),
     data.new(array.new<float>(),'20'),
     data.new(array.new<float>(),'21'),
     data.new(array.new<float>(),'22'),
     data.new(array.new<float>(),'23'))

parsedDayofweek     = dayofweek(time_tradingday)
parsedDayofmonth    = syminfo.type == 'crypto' ? dayofmonth : dayofmonth(time_close)
parsedMonth         = syminfo.type == 'crypto' ? month      : month(time_close)

var parsedDashboardPosition = switch dashboardPositionInput
    TOP_RIGHT       => position.top_right
    BOTTOM_RIGHT    => position.bottom_right
    BOTTOM_LEFT     => position.bottom_left

var parsedDashboardSize     = switch dashboardSizeInput
    TINY            => size.tiny
    SMALL           => size.small
    NORMAL          => size.normal
    LARGE           => size.large
    HUGE            => size.huge

var int optimalTimeframe    = switch periodInput
    DAYOFWEEK   => timeframe.in_seconds('1D')
    DAYOFMONTH  => timeframe.in_seconds('1D')
    MONTH       => timeframe.in_seconds('1M')
    HOURS       => timeframe.in_seconds('60')

[cold,lukewarm,hot] = switch colormapInput
    VIRIDIS => [COLD_VIRIDIS,LUKEWARM_VIRIDIS,HOT_VIRIDIS]
    PLASMA  => [COLD_PLASMA,LUKEWARM_PLASMA,HOT_VIRIDIS]
    MAGMA   => [COLD_MAGMA,LUKEWARM_MAGMA,HOT_MAGMA]
    CUSTOM  => [coldColorInput,lukewarmColorInput,hotColorInput]

//---------------------------------------------------------------------------------------------------------------------}
//USER-DEFINED FUNCTIONS
//---------------------------------------------------------------------------------------------------------------------{
error() =>
    var table t_able= table.new(position.top_center,1,1
     , bgcolor      = ORANGE_LIGHT
     , border_color = ORANGE
     , border_width = 1
     , frame_color  = ORANGE
     , frame_width  = 1)

    t_able.cell(0,0,'⚠ Warning: The tool will not display the dashboard on higher timeframes.\nPlease select a chart timeframe that is equal to or lower than the period parameter in the settings panel.',text_color = ORANGE, text_size = 0)

gatherDaysOfWeek(float output) =>    
    [dailyData,currentDay] = request.security(syminfo.tickerid, '1D', [output,parsedDayofweek])
    data current = fullWeek.get(currentDay - 1)
    if not na(current)
        current.values.push(nz(dailyData))    

gatherMonths(float output) =>
    [montlhyData,currentMonth] = request.security(syminfo.tickerid, '1M', [output,parsedMonth])
    data current = fullYear.get(currentMonth - 1)
    if not na(current)
        current.values.push(nz(montlhyData))    

gatherDayOfMonth(float output) =>
    [dailyData,dayOfMonth] = request.security(syminfo.tickerid, '1D', [output,parsedDayofmonth])
    data current = fullMonth.get(dayOfMonth - 1)
    if not na(current)
        current.values.push(nz(dailyData))    

gatherHours(float output) =>
    [hourlyData,currentHour] = request.security(syminfo.tickerid, '60', [output,hour])
    data current = fullDay.get(currentHour)
    if not na(current)
        current.values.push(nz(hourlyData))    
         
gatherData() =>
    float output = baseDataInput == VOLUME ? volume : ta.tr
    switch periodInput
        DAYOFWEEK   => gatherDaysOfWeek(output)
        DAYOFMONTH  => gatherDayOfMonth(output)
        MONTH       => gatherMonths(output)
        HOURS       => gatherHours(output)

cell(table t_able, int column, int row, string data, color = #FFFFFF, align = text.align_center, color background = na, float height = 0) => t_able.cell(column,row,data,text_color = color, text_size = parsedDashboardSize, text_halign = align, bgcolor = background, height = height)

divider(table t_able, int row, int column) =>    
    string rowDivider = '━━━━━━━━━━━'
    t_able.merge_cells(0,row,column,row)    
    cell(t_able,0,row,rowDivider,align = text.align_center, height = 0.5*dashboardHeightInput, color = DATA)

displayColumns(array<data> fullData) =>
    var table t_able    = table.new(parsedDashboardPosition,fullData.size()*2 + 1,104,
        bgcolor          = BACKGROUND,
        border_width     = 0,
        frame_color      = BORDERS,
        frame_width      = 1,
        force_overlay    = false)

    var array<float> parsedData = array.new<float>()   

    for [index,eachDataPoint] in fullData
        parsedData.push(eachDataPoint.values.median())

    t_able.merge_cells(0,0,fullData.size()*2 - 2,0)
    t_able.merge_cells(0,1,fullData.size()*2 - 2,1)    
    string min = str.tostring(parsedData.min(),baseDataInput == VOLATILITY ? format.mintick : format.volume)
    string max = str.tostring(parsedData.max(),baseDataInput == VOLATILITY ? format.mintick : format.volume)

    string periodTag = switch periodInput
        MONTH   => 'Monthly'
        HOURS   => 'Hourly'
        => 'Daily'
    string header = deltaInput ? '(Delta)' : ''
    cell(t_able,0,0,str.format('{0} Median {1} {2}\nMin: {3}  Max: {4}',periodTag,baseDataInput,header,min,max),align = text.align_center, color = DATA)

    int column = 0
    for [index,eachDataPoint] in parsedData
        if index > 0
            column += 2

        t_able.merge_cells(column,2,column,103)            
        t_able.cell_set_height(column,2,0.2*dashboardHeightInput)

        float scale = deltaInput ? (eachDataPoint-parsedData.min())/parsedData.range() : eachDataPoint/parsedData.max()
        int value = math.max(1,math.floor(100*scale))        
        color customColor = value >= 50 ? color.from_gradient(value,50,100,color.new(lukewarm,0),color.new(hot,0)) : color.from_gradient(value,1,50,color.new(cold,0),color.new(lukewarm,0))

        int topRow = 101-value
        for row = 1 to 100
            t_able.cell(column + 1,row + 1,height = 0.1*dashboardHeightInput)
            if row >= topRow
                t_able.cell_set_bgcolor(column + 1,row + 1,customColor)
        
        t_able.cell(column + 1,103,fullData.get(index).tag,width = dashboardWidthInput, text_color = DATA, text_size = parsedDashboardSize)           
        
        if index == (parsedData.size() - 1)
            t_able.merge_cells(column + 2,2,column + 2,103)            
            t_able.cell_set_height(column + 2,2,0.2*dashboardHeightInput)

displayMonth() =>
    var table t_able    = table.new(parsedDashboardPosition,9,7,
     bgcolor          = BACKGROUND,
     border_width     = 0,
     frame_color      = BORDERS,
     frame_width      = 1,
     force_overlay    = false)

    var array<float> parsedData = array.new<float>()

    for [index,eachDataPoint] in fullMonth
        parsedData.push(eachDataPoint.values.median())

    t_able.merge_cells(0,0,6,0)
    string min = str.tostring(parsedData.min(),baseDataInput == VOLATILITY ? format.mintick : format.volume)
    string max = str.tostring(parsedData.max(),baseDataInput == VOLATILITY ? format.mintick : format.volume)

    string periodTag = switch periodInput
        MONTH   => 'Monthly'
        HOURS   => 'Hourly'
        => 'Daily'

    string header = deltaInput ? '(Delta)' : ''
    cell(t_able,0,0,str.format('{0} Median {1} {2}\nMax: {3}\nMin: {4}',periodTag,baseDataInput,header,max,min),align = text.align_center, color = DATA)

    divider(t_able,1,6)
        
    int row = 2
    array<int> column0 = array.from(1,8,15,22,29)
    array<int> column1 = array.from(2,9,16,23,30)
    array<int> column2 = array.from(3,10,17,24,31)
    array<int> column3 = array.from(4,11,18,25)
    array<int> column4 = array.from(5,12,19,26)
    array<int> column5 = array.from(6,13,20,27)
    array<int> column6 = array.from(7,14,21,28)

    bool nextRow = false

    for [index,eachDataPoint] in parsedData
        int column = switch
            column0.includes(index + 1) => 0
            column1.includes(index + 1) => 1
            column2.includes(index + 1) => 2
            column3.includes(index + 1) => 3
            column4.includes(index + 1) => 4
            column5.includes(index + 1) => 5
            column6.includes(index + 1) => 6            

        if nextRow
            row += 1
            nextRow := false

        if (index + 1) % 7 == 0 and not nextRow
            nextRow := true         

        float scale = deltaInput ? (eachDataPoint-parsedData.min())/parsedData.range() : eachDataPoint/parsedData.max()
        int value = math.max(1,math.floor(100*scale))        
        color customColor = value >= 50 ? color.from_gradient(value,50,100,color.new(lukewarm,0),color.new(hot,0)) : color.from_gradient(value,1,50,color.new(cold,0),color.new(lukewarm,0))
        cell(t_able,column,row,str.tostring(index + 1),background = customColor)   

displayData() =>
    switch periodInput
        DAYOFWEEK   => displayColumns(fullWeek)
        DAYOFMONTH  => displayMonth()
        MONTH       => displayColumns(fullYear)
        HOURS       => displayColumns(fullDay)

//---------------------------------------------------------------------------------------------------------------------}
//MUTABLE VARIABLES & EXECUTION
//---------------------------------------------------------------------------------------------------------------------{
if timeframe.in_seconds() > optimalTimeframe
    error()
else
    gatherData()

    if barstate.islastconfirmedhistory
        displayData()

//---------------------------------------------------------------------------------------------------------------------}

Overview

Peak Trading Activity Graphs [LuxAlgo] evaluates volume or volatility across selected periods, including hours of the day, days of the week, days of the month, or months of the year. The indicator visualizes relative activity levels using heatmaps such as Viridis, Plasma, Magma, or custom colors, with an optional median delta calculation to highlight differences between values. Users can customize dashboard dimensions and screen placement. It provides visual analytics with no trade signals.

Details

Published by

MeridianAlgo on GitHub

Code credit

LuxAlgo, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Volume, Volatility & bands, Sessions & time, Screeners & dashboards

Markets

Any symbol

Timeframes

Any timeframe

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

code-converterconverterjavascriptpinescripttranspilercode-reviewmeridianalgocode-conversionpinescript-algorithms

Settings

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

SettingTypeDefaultVariable
DatastringVOLUMEbaseDataInput
PeriodstringDAYOFMONTHperiodInput
Display Delta Between MediansboolfalsedeltaInput
PositionstringTOP_RIGHTdashboardPositionInput
SizestringSMALLdashboardSizeInput
Widthfloat1.0dashboardWidthInput
Heightfloat1.0dashboardHeightInput
ColorsstringVIRIDIScolormapInput
Custom ColdcolorREDcoldColorInput
Custom LukewarmcolorYELLOWlukewarmColorInput
Custom HotcolorGREENhotColorInput
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

92 stars

This indicator classifies the market into gamma regime types (negative gamma trend, positive gamma mean-reversion, or transition) by calculating a composite score based on daily ATR ratios, session VWAP crossover frequencies, and opening range displacement. It

Indicator v5 Volatility & bandsTrend
Updated 8 weeks ago · Custom licence GitHub repository of Moon Dev GEX Regime & Trap
56 stars

This indicator marks forex trading sessions and opening ranges on the price chart. It can draw Sydney, Tokyo, Frankfurt, London, and New York session boxes or five- and fifteen-minute opening ranges, plus an optional opening channel and a day-open range. Day r

Indicator v5 Sessions & timeSupport & resistance Repaints
Updated 2 months ago · None stated GitHub repository of Day Trading Booster by DGT

AM TBR — NQ Stats

by TamaraKGreen
46 stars

The AM TBR indicator calculates statistical levels for NQ futures using standard deviation. It plots a sigma ladder showing touch levels at 0.5 to 2.0 standard deviations, marks touch points, displays Mean Absolute Error (MAE) zones after touches, and shows Me

Indicator v6 Volatility & bandsSupport & resistance
Updated 7 weeks ago · None stated GitHub repository of AM TBR — NQ Stats

LoD Intraday Entry

by ChartsLector
41 stars

This overlay indicator marks the current day's low and a suggested entry zone a fraction of daily ATR below that low. It compares daily volume with a volume moving average, plots daily SMA and EMA distance, and draws an optional ATR-width price cloud. Labels c

Indicator v6 Support & resistanceVolatility & bands
Updated 2 days ago · None stated GitHub repository of LoD Intraday Entry

Zen Trading Toolkit

by Zen-Tim
29 stars

This indicator plots average bar range (ABR) measured move targets, opening range boxes, and historical key reference levels directly on the price chart. It projects bullish and bearish targets (0.5x, 1.0x, 1.5x, 2.0x ABR) from the previous session's close, al

Indicator v6 Support & resistanceVolatility & bands
Updated 2 weeks ago · MIT GitHub repository of Zen Trading Toolkit

Equity Intraday Analyst

by chris-c-thomas
14 stars

Equity Intraday Analyst is a comprehensive intraday overlay indicator for US equities and ETFs. It evaluates multi-EMA ribbons, session-anchored VWAP bands, OBV trends, momentum oscillators, TTM Squeeze, and optional market breadth indicators like NYSE TICK an

Indicator v6 TrendVolume
Updated 6 months ago · MIT GitHub repository of Equity Intraday Analyst

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