Indicator · Pine Script v6 · On the chart

4H Candle Curves

by TamaraKGreen· code credited to notprofessorgreen

4H Candle Curves highlights 4-hour trading sessions based on New York time by overlaying customizable colored boxes directly on the main price chart.

View on GitHub

GitHub stars46on the repository
Pine versionv6current
Lines of code604
Settings18inputs
LicenceNone statedfile: MPL-2.0
Last update7 weeks ago4 Sep 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

604 lines of Pine Script v6 from 4h Candle Curves.txt, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

4h Candle Curves.txt
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © notprofessorgreen

//@version=6
indicator(title="4H Candle Curves", shorttitle="4H Curves", overlay=true)

// === Input Definitions ===
time_zone = "America/New_York"

// 4H Candle Sessions (24-hour cycle starting 6pm EST)
session_6pm = "1800-2200"   // 6pm-10pm
session_10pm = "2200-0200"  // 10pm-2am  
session_2am = "0200-0600"   // 2am-6am
session_6am = "0600-1000"   // 6am-10am
session_10am = "1000-1400"  // 10am-2pm
session_2pm = "1400-1700"   // 2pm-5pm (only 3 hours)

// Display Settings
show_4h_boxes = input.bool(true, "Show 4H Candle Boxes", group="Display")
box_color_6pm = input.color(color.new(color.blue, 90), "6pm Box", inline="boxes", group="Display")
box_color_10pm = input.color(color.new(color.purple, 90), "10pm Box", inline="boxes", group="Display")
box_color_2am = input.color(color.new(color.orange, 90), "2am Box", inline="boxes", group="Display")
box_color_6am = input.color(color.new(color.green, 90), "6am Box", inline="boxes2", group="Display")
box_color_10am = input.color(color.new(color.red, 90), "10am Box", inline="boxes2", group="Display")
box_color_2pm = input.color(color.new(color.yellow, 90), "2pm Box", inline="boxes2", group="Display")

show_quarter_lines = input.bool(true, "Show Quarter Separators (1H, 2H, 3H)", group="Display")
show_quarter_labels = input.bool(true, "Show Candle Name Labels (at 2H mark)", group="Display")
quarter_line_color = input.color(color.gray, "Separator Line Color", group="Display")
quarter_line_style = input.string("Dotted", "Separator Line Style", options=["Solid", "Dashed", "Dotted"], group="Display")
quarter_line_width = input.int(1, "Separator Line Width", minval=1, maxval=4, group="Display")

max_candles = input.int(20, "Max Historical 4H Candles", minval=1, maxval=50, group="Display")

// Table Settings
show_live_table = input.bool(true, "Show Live Status Table", group="Tables")
live_table_position = input.string("Bottom-Right", "Live Table Position", options=["Top-Left", "Top-Right", "Bottom-Left", "Bottom-Right"], group="Tables")
show_reference_table = input.bool(true, "Show Curve Reference Table", group="Tables")
reference_table_position = input.string("Bottom-Left", "Reference Table Position", options=["Top-Left", "Top-Right", "Bottom-Left", "Bottom-Right"], group="Tables")
table_text_size = input.string("Small", "Table Text Size", options=["Tiny", "Small", "Normal"], group="Tables")

// === Helper Functions ===
get_line_style(style_string) =>
    style_string == "Solid" ? line.style_solid : style_string == "Dashed" ? line.style_dashed : line.style_dotted

get_text_size(size_string) =>
    size_string == "Tiny" ? size.tiny : size_string == "Small" ? size.small : size.normal

get_table_position(pos_string) =>
    pos_string == "Top-Left" ? position.top_left : pos_string == "Top-Right" ? position.top_right : pos_string == "Bottom-Left" ? position.bottom_left : position.bottom_right

get_box_color(candle_name) =>
    candle_name == "6pm" ? box_color_6pm : candle_name == "10pm" ? box_color_10pm : candle_name == "2am" ? box_color_2am : candle_name == "6am" ? box_color_6am : candle_name == "10am" ? box_color_10am : box_color_2pm

// === Session Detection ===
is_in_6pm = not na(time(timeframe.period, session_6pm, time_zone))
is_in_10pm = not na(time(timeframe.period, session_10pm, time_zone))
is_in_2am = not na(time(timeframe.period, session_2am, time_zone))
is_in_6am = not na(time(timeframe.period, session_6am, time_zone))
is_in_10am = not na(time(timeframe.period, session_10am, time_zone))
is_in_2pm = not na(time(timeframe.period, session_2pm, time_zone))

// === Data Tracking ===
var string current_candle = ""
var float candle_open = na
var float candle_high = na
var float candle_low = na
var float candle_close = na
var int candle_start_bar = na
var int candle_id = 0

var float q1_high = na
var float q1_low = na
var float q2_high = na
var float q2_low = na
var float q3_high = na
var float q3_low = na
var float q4_high = na
var float q4_low = na
var int q1_end_bar = na
var int q2_end_bar = na
var int q3_end_bar = na
var bool q2_broke_q1_high = false
var bool q2_broke_q1_low = false
var bool second_half_made_new_low_below_q1 = false  // For CURVE when Q2 broke high
var bool second_half_made_new_high_above_q1 = false // For CURVE when Q2 broke low
var bool second_half_made_new_high_above_q2 = false // For CONTINUATION when Q2 broke high
var bool second_half_made_new_low_below_q2 = false  // For CONTINUATION when Q2 broke low
var string current_quarter_name = ""

var box current_box = na
var line q1_q2_line = na
var line q2_q3_line = na
var line q3_q4_line = na
var label q2_q3_label = na

var box[] all_boxes = array.new_box(0)
var line[] all_quarter_lines = array.new_line(0)
var label[] all_quarter_labels = array.new_label(0)
var int[] candle_ids = array.new_int(0)

// Determine which candle we're in
string new_candle = ""
if is_in_6pm
    new_candle := "6pm"
else if is_in_10pm
    new_candle := "10pm"
else if is_in_2am
    new_candle := "2am"
else if is_in_6am
    new_candle := "6am"
else if is_in_10am
    new_candle := "10am"
else if is_in_2pm
    new_candle := "2pm"

// Detect new candle start
bool candle_started = new_candle != "" and new_candle != current_candle

if candle_started
    // Store previous candle if it exists
    if current_candle != "" and not na(current_box)
        array.push(all_boxes, current_box)
        array.push(candle_ids, candle_id)
        current_box := na
        
        if not na(q1_q2_line)
            array.push(all_quarter_lines, q1_q2_line)
        if not na(q2_q3_line)
            array.push(all_quarter_lines, q2_q3_line)
        if not na(q3_q4_line)
            array.push(all_quarter_lines, q3_q4_line)
        if not na(q2_q3_label)
            array.push(all_quarter_labels, q2_q3_label)
        
        q1_q2_line := na
        q2_q3_line := na
        q3_q4_line := na
        q2_q3_label := na
    
    // Start new candle
    current_candle := new_candle
    candle_id := candle_id + 1
    candle_open := open
    candle_high := high
    candle_low := low
    candle_start_bar := bar_index
    q1_high := high
    q1_low := low
    q2_high := na
    q2_low := na
    q3_high := na
    q3_low := na
    q4_high := na
    q4_low := na
    q1_end_bar := na
    q2_end_bar := na
    q3_end_bar := na
    q2_broke_q1_high := false
    q2_broke_q1_low := false
    second_half_made_new_low_below_q1 := false
    second_half_made_new_high_above_q1 := false
    second_half_made_new_high_above_q2 := false
    second_half_made_new_low_below_q2 := false
    current_quarter_name := "Q1 (Hour 1)"
    q1_q2_line := na
    q2_q3_line := na
    q3_q4_line := na
    q2_q3_label := na
    
    // Create new box
    if show_4h_boxes
        current_box := box.new(
             left=candle_start_bar,
             top=candle_high,
             right=bar_index,
             bottom=candle_low,
             bgcolor=get_box_color(current_candle),
             border_color=color.gray,
             border_width=1
         )

else if new_candle != "" and new_candle == current_candle
    // Update current candle
    candle_high := math.max(candle_high, high)
    candle_low := math.min(candle_low, low)
    candle_close := close
    
    // Calculate time elapsed in minutes
    int bars_elapsed = bar_index - candle_start_bar
    int minutes_per_bar = timeframe.in_seconds(timeframe.period) / 60
    int minutes_elapsed = bars_elapsed * minutes_per_bar
    
    // Determine which quarter we're in
    bool in_q1 = minutes_elapsed < 60
    bool in_q2 = minutes_elapsed >= 60 and minutes_elapsed < 120
    bool in_q3 = minutes_elapsed >= 120 and minutes_elapsed < 180
    bool in_q4 = minutes_elapsed >= 180
    
    // Update current quarter name
    if in_q1
        current_quarter_name := "Q1 (Hour 1)"
    else if in_q2
        current_quarter_name := "Q2 (Hour 2)"
    else if in_q3
        current_quarter_name := "Q3 (Hour 3)"
    else if in_q4
        current_quarter_name := "Q4 (Hour 4)"
    
    // Track Q1 (hour 0-1)
    if in_q1
        q1_high := math.max(q1_high, high)
        q1_low := math.min(q1_low, low)
    
    // Track Q2 (hour 1-2)
    else if in_q2
        if na(q2_high)
            q2_high := high
            q2_low := low
            q1_end_bar := bar_index
            
            // Draw line at Q1/Q2 boundary
            if show_quarter_lines and na(q1_q2_line)
                q1_q2_line := line.new(
                     x1=bar_index,
                     y1=candle_low,
                     x2=bar_index,
                     y2=candle_high,
                     xloc=xloc.bar_index,
                     color=quarter_line_color,
                     style=get_line_style(quarter_line_style),
                     width=quarter_line_width
                 )
        else
            q2_high := math.max(q2_high, high)
            q2_low := math.min(q2_low, low)
        
        // Check if Q2 broke Q1 range
        if not na(q1_high) and not na(q1_low)
            if q2_high > q1_high
                q2_broke_q1_high := true
            if q2_low < q1_low
                q2_broke_q1_low := true
    
    // Track Q3 (hour 2-3)
    else if in_q3
        if na(q3_high)
            q3_high := high
            q3_low := low
            q2_end_bar := bar_index
            
            // Draw line at Q2/Q3 boundary
            if show_quarter_lines and na(q2_q3_line)
                q2_q3_line := line.new(
                     x1=bar_index,
                     y1=candle_low,
                     x2=bar_index,
                     y2=candle_high,
                     xloc=xloc.bar_index,
                     color=quarter_line_color,
                     style=get_line_style(quarter_line_style),
                     width=quarter_line_width
                 )
                
                if show_quarter_labels
                    q2_q3_label := label.new(
                         x=bar_index,
                         y=candle_high,
                         text=current_candle,
                         color=color.new(color.gray, 80),
                         textcolor=quarter_line_color,
                         style=label.style_none,
                         size=get_text_size(table_text_size),
                         text_font_family=font.family_monospace
                     )
        else
            q3_high := math.max(q3_high, high)
            q3_low := math.min(q3_low, low)
        
        // Check what happened in second half
        if not na(q1_high) and not na(q1_low) and not na(q2_high) and not na(q2_low) and not na(q3_high) and not na(q3_low)
            // Check for CURVE patterns (relative to Q1)
            if q3_low < q1_low
                second_half_made_new_low_below_q1 := true
            if q3_high > q1_high
                second_half_made_new_high_above_q1 := true
            
            // Check for CONTINUATION patterns (relative to Q2)
            if q3_high > q2_high
                second_half_made_new_high_above_q2 := true
            if q3_low < q2_low
                second_half_made_new_low_below_q2 := true
    
    // Track Q4 (hour 3-4)
    else if in_q4
        if na(q4_high)
            q4_high := high
            q4_low := low
            q3_end_bar := bar_index
            
            // Draw line at Q3/Q4 boundary
            if show_quarter_lines and na(q3_q4_line)
                q3_q4_line := line.new(
                     x1=bar_index,
                     y1=candle_low,
                     x2=bar_index,
                     y2=candle_high,
                     xloc=xloc.bar_index,
                     color=quarter_line_color,
                     style=get_line_style(quarter_line_style),
                     width=quarter_line_width
                 )
        else
            q4_high := math.max(q4_high, high)
            q4_low := math.min(q4_low, low)
        
        // Check what happened in second half
        if not na(q1_high) and not na(q1_low) and not na(q2_high) and not na(q2_low)
            float second_half_high = math.max(na(q3_high) ? q4_high : q3_high, q4_high)
            float second_half_low = math.min(na(q3_low) ? q4_low : q3_low, q4_low)
            
            // Check for CURVE patterns (relative to Q1)
            if second_half_low < q1_low
                second_half_made_new_low_below_q1 := true
            if second_half_high > q1_high
                second_half_made_new_high_above_q1 := true
            
            // Check for CONTINUATION patterns (relative to Q2)
            if second_half_high > q2_high
                second_half_made_new_high_above_q2 := true
            if second_half_low < q2_low
                second_half_made_new_low_below_q2 := true
    
    // Update all quarter lines
    if not na(q1_q2_line)
        line.set_y1(q1_q2_line, candle_low)
        line.set_y2(q1_q2_line, candle_high)
    
    if not na(q2_q3_line)
        line.set_y1(q2_q3_line, candle_low)
        line.set_y2(q2_q3_line, candle_high)
    
    if not na(q3_q4_line)
        line.set_y1(q3_q4_line, candle_low)
        line.set_y2(q3_q4_line, candle_high)
    
    if show_quarter_labels and not na(q2_q3_label)
        label.set_y(q2_q3_label, candle_high)
        
        // Update label based on Q2 breaks
        if q2_broke_q1_high and q2_broke_q1_low
            label.set_text(q2_q3_label, current_candle + " ⚠️")
            label.set_color(q2_q3_label, color.new(color.orange, 70))
        else if q2_broke_q1_high
            label.set_text(q2_q3_label, current_candle + " 🔼")
            label.set_color(q2_q3_label, color.new(color.green, 70))
        else if q2_broke_q1_low
            label.set_text(q2_q3_label, current_candle + " 🔽")
            label.set_color(q2_q3_label, color.new(color.red, 70))
    
    // Update current box
    if show_4h_boxes and not na(current_box)
        box.set_top(current_box, candle_high)
        box.set_bottom(current_box, candle_low)
        box.set_right(current_box, bar_index)

// Clean up old candles
if array.size(all_boxes) > max_candles
    box.delete(array.shift(all_boxes))
    array.shift(candle_ids)
    
if array.size(all_quarter_lines) > max_candles
    line.delete(array.shift(all_quarter_lines))

if array.size(all_quarter_labels) > max_candles
    label.delete(array.shift(all_quarter_labels))

// === TABLE 1: LIVE STATUS ===
var table live_table = na

if show_live_table and barstate.islast and current_candle != ""
    if na(live_table)
        live_table := table.new(
             position=get_table_position(live_table_position),
             columns=2,
             rows=8,
             bgcolor=color.new(color.white, 10),
             border_color=color.black,
             border_width=1
         )
    
    // Get curve statistics for current candle - CORRECTED VALUES
    float curve_pct_if_q2_breaks_high = na
    float continuation_pct_if_q2_breaks_high = na
    float curve_pct_if_q2_breaks_low = na
    float continuation_pct_if_q2_breaks_low = na
    
    if current_candle == "6pm"
        curve_pct_if_q2_breaks_high := 29.6
        continuation_pct_if_q2_breaks_high := 59.0  // CORRECTED from 67.8%
        curve_pct_if_q2_breaks_low := 33.7
        continuation_pct_if_q2_breaks_low := 51.1   // CORRECTED from 63.7%
    else if current_candle == "10pm"
        curve_pct_if_q2_breaks_high := 27.5
        continuation_pct_if_q2_breaks_high := 55.1  // CORRECTED from 69.8%
        curve_pct_if_q2_breaks_low := 33.1
        continuation_pct_if_q2_breaks_low := 48.6   // CORRECTED from 63.8%
    else if current_candle == "2am"
        curve_pct_if_q2_breaks_high := 38.4
        continuation_pct_if_q2_breaks_high := 46.7  // CORRECTED from 58.5%
        curve_pct_if_q2_breaks_low := 42.8
        continuation_pct_if_q2_breaks_low := 43.3   // CORRECTED from 54.0%
    else if current_candle == "6am"
        curve_pct_if_q2_breaks_high := 60.6
        continuation_pct_if_q2_breaks_high := 38.5
        curve_pct_if_q2_breaks_low := 64.4
        continuation_pct_if_q2_breaks_low := 35.0
    else if current_candle == "10am"
        curve_pct_if_q2_breaks_high := 17.2
        continuation_pct_if_q2_breaks_high := 60.4
        curve_pct_if_q2_breaks_low := 16.7
        continuation_pct_if_q2_breaks_low := 51.6
    
    // Determine status
    bool in_second_half = not na(q3_high) or not na(q4_high)
    string status = ""
    string expectation = ""
    color status_color = color.white
    
    if in_second_half
        // We're in second half - report what happened
        if q2_broke_q1_high and second_half_made_new_low_below_q1
            status := "✓ CURVE CONFIRMED"
            expectation := "Q2 broke high → 2nd half made new LOW below Q1\nCurve: " + str.tostring(curve_pct_if_q2_breaks_high, "#.#") + "%"
            status_color := color.new(color.purple, 70)
        else if q2_broke_q1_high and second_half_made_new_high_above_q2
            status := "✓ CONTINUATION CONFIRMED"
            expectation := "Q2 broke high → 2nd half made new HIGH above Q2\nContinuation: " + str.tostring(continuation_pct_if_q2_breaks_high, "#.#") + "%"
            status_color := color.new(color.green, 70)
        else if q2_broke_q1_low and second_half_made_new_high_above_q1
            status := "✓ CURVE CONFIRMED"
            expectation := "Q2 broke low → 2nd half made new HIGH above Q1\nCurve: " + str.tostring(curve_pct_if_q2_breaks_low, "#.#") + "%"
            status_color := color.new(color.purple, 70)
        else if q2_broke_q1_low and second_half_made_new_low_below_q2
            status := "✓ CONTINUATION CONFIRMED"
            expectation := "Q2 broke low → 2nd half made new LOW below Q2\nContinuation: " + str.tostring(continuation_pct_if_q2_breaks_low, "#.#") + "%"
            status_color := color.new(color.red, 70)
        else if q2_broke_q1_high or q2_broke_q1_low
            status := "⏳ 2nd Half In Progress"
            if q2_broke_q1_high
                expectation := "Q2 broke high - watching for:\nCurve (low < Q1): " + str.tostring(curve_pct_if_q2_breaks_high, "#.#") + "%\nCont (high > Q2): " + str.tostring(continuation_pct_if_q2_breaks_high, "#.#") + "%"
            else
                expectation := "Q2 broke low - watching for:\nCurve (high > Q1): " + str.tostring(curve_pct_if_q2_breaks_low, "#.#") + "%\nCont (low < Q2): " + str.tostring(continuation_pct_if_q2_breaks_low, "#.#") + "%"
            status_color := color.new(color.blue, 80)
        else
            status := "📊 No Q2 Break Occurred"
            expectation := "Q2 stayed within Q1 range"
            status_color := color.new(color.yellow, 80)
    else
        // Still in first half
        if q2_broke_q1_high and q2_broke_q1_low
            status := "⚠️ Q2 Broke BOTH Q1 Extremes"
            expectation := "Extended range - watch 2nd half closely"
            status_color := color.new(color.orange, 80)
        else if q2_broke_q1_high
            status := "🔼 Q2 Broke Q1 HIGH"
            expectation := "EXPECT 2nd half:\nCURVE (low < Q1): " + str.tostring(curve_pct_if_q2_breaks_high, "#.#") + "%\nCONT (high > Q2): " + str.tostring(continuation_pct_if_q2_breaks_high, "#.#") + "%"
            status_color := color.new(color.green, 80)
        else if q2_broke_q1_low
            status := "🔽 Q2 Broke Q1 LOW"
            expectation := "EXPECT 2nd half:\nCURVE (high > Q1): " + str.tostring(curve_pct_if_q2_breaks_low, "#.#") + "%\nCONT (low < Q2): " + str.tostring(continuation_pct_if_q2_breaks_low, "#.#") + "%"
            status_color := color.new(color.red, 80)
        else if not na(q2_high)
            status := "⏳ Q2 Building..."
            expectation := "Q2 within Q1 range so far"
            status_color := color.new(color.gray, 85)
        else
            status := "⏳ Q1 Building..."
            expectation := "First hour forming"
            status_color := color.new(color.gray, 85)
    
    // Build table
    table.cell(live_table, 0, 0, "LIVE: " + current_candle + " CANDLE", bgcolor=get_box_color(current_candle), text_color=color.gray, text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(live_table, 0, 0, 1, 0)
    
    table.cell(live_table, 0, 1, "Quarter: " + current_quarter_name, bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(live_table, 0, 1, 1, 1)
    
    table.cell(live_table, 0, 2, "", text_size=size.tiny)
    table.merge_cells(live_table, 0, 2, 1, 2)
    
    table.cell(live_table, 0, 3, "STATUS", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(live_table, 0, 3, 1, 3)
    
    table.cell(live_table, 0, 4, status, bgcolor=status_color, text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(live_table, 0, 4, 1, 4)
    
    table.cell(live_table, 0, 5, "", text_size=size.tiny)
    table.merge_cells(live_table, 0, 5, 1, 5)
    
    table.cell(live_table, 0, 6, "EXPECTATION", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(live_table, 0, 6, 1, 6)
    
    table.cell(live_table, 0, 7, expectation, text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(live_table, 0, 7, 1, 7)

// === TABLE 2: CURVE REFERENCE - CORRECTED VALUES ===
var table ref_table = na

if show_reference_table and barstate.islast
    if na(ref_table)
        ref_table := table.new(
             position=get_table_position(reference_table_position),
             columns=4,
             rows=16,
             bgcolor=color.new(color.white, 10),
             border_color=color.black,
             border_width=1
         )
    
    // Header
    table.cell(ref_table, 0, 0, "CURVE REFERENCE", bgcolor=color.new(color.blue, 70), text_color=color.gray, text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(ref_table, 0, 0, 3, 0)
    
    table.cell(ref_table, 0, 1, "When Q2 BREAKS Q1 HIGH", bgcolor=color.new(color.green, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(ref_table, 0, 1, 3, 1)
    
    table.cell(ref_table, 0, 2, "Session", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 2, "Curve", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 2, "Cont", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 2, "Winner", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 6AM
    table.cell(ref_table, 0, 3, "6AM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 3, "60.6%", bgcolor=color.new(color.purple, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 3, "38.5%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 3, "🔥 CURVE", bgcolor=color.new(color.purple, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 2AM - CORRECTED
    table.cell(ref_table, 0, 4, "2AM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 4, "38.4%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 4, "46.7%", bgcolor=color.new(color.blue, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 4, "Cont", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 6PM - CORRECTED
    table.cell(ref_table, 0, 5, "6PM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 5, "29.6%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 5, "59.0%", bgcolor=color.new(color.blue, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 5, "CONT", bgcolor=color.new(color.blue, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 10PM - CORRECTED
    table.cell(ref_table, 0, 6, "10PM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 6, "27.5%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 6, "55.1%", bgcolor=color.new(color.blue, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 6, "CONT", bgcolor=color.new(color.blue, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 10AM
    table.cell(ref_table, 0, 7, "10AM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 7, "17.2%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 7, "60.4%", bgcolor=color.new(color.blue, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 7, "CONT", bgcolor=color.new(color.blue, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // Separator
    table.cell(ref_table, 0, 8, "", text_size=size.tiny)
    table.merge_cells(ref_table, 0, 8, 3, 8)
    
    // Second section header
    table.cell(ref_table, 0, 9, "When Q2 BREAKS Q1 LOW", bgcolor=color.new(color.red, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.merge_cells(ref_table, 0, 9, 3, 9)
    
    table.cell(ref_table, 0, 10, "Session", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 10, "Curve", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 10, "Cont", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 10, "Winner", bgcolor=color.new(color.gray, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 6AM
    table.cell(ref_table, 0, 11, "6AM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 11, "64.4%", bgcolor=color.new(color.purple, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 11, "35.0%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 11, "🔥 CURVE", bgcolor=color.new(color.purple, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 2AM - CORRECTED
    table.cell(ref_table, 0, 12, "2AM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 12, "42.8%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 12, "43.3%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 12, "Balanced", bgcolor=color.new(color.yellow, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 6PM - CORRECTED
    table.cell(ref_table, 0, 13, "6PM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 13, "33.7%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 13, "51.1%", bgcolor=color.new(color.blue, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 13, "CONT", bgcolor=color.new(color.blue, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 10PM - CORRECTED
    table.cell(ref_table, 0, 14, "10PM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 14, "33.1%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 14, "48.6%", bgcolor=color.new(color.blue, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 14, "CONT", bgcolor=color.new(color.blue, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    
    // 10AM
    table.cell(ref_table, 0, 15, "10AM", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 1, 15, "16.7%", text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 2, 15, "51.6%", bgcolor=color.new(color.blue, 90), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)
    table.cell(ref_table, 3, 15, "CONT", bgcolor=color.new(color.blue, 80), text_size=get_text_size(table_text_size), text_font_family=font.family_monospace)

Overview

4H Candle Curves divides 4-hour sessions with quarter separator lines at one-hour intervals and places name labels at the mid-point. Optional on-screen tables provide a live status view and a curve reference legend. Users can customize session box colors, line styles, historical candle limits, and table placements. It serves as a visual session map for lower timeframe analysis and does not generate trading signals.

Details

Published by

TamaraKGreen on GitHub

Code credit

notprofessorgreen, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Sessions & time, Screeners & dashboards, Support & resistance

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.

Settings

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

SettingTypeDefaultVariable
Show 4H Candle Boxesbooltrueshow_4h_boxes
6pm Boxcolorcolor.new(color.blue, 90)box_color_6pm
10pm Boxcolorcolor.new(color.purple, 90)box_color_10pm
2am Boxcolorcolor.new(color.orange, 90)box_color_2am
6am Boxcolorcolor.new(color.green, 90)box_color_6am
10am Boxcolorcolor.new(color.red, 90)box_color_10am
2pm Boxcolorcolor.new(color.yellow, 90)box_color_2pm
Show Quarter Separators (1H, 2H, 3H)booltrueshow_quarter_lines
Show Candle Name Labels (at 2H mark)booltrueshow_quarter_labels
Separator Line Colorcolorcolor.grayquarter_line_color
Separator Line StylestringDottedquarter_line_style
Separator Line Widthint1quarter_line_width
Show all 18 settings
SettingTypeDefaultVariable
Max Historical 4H Candlesint20max_candles
Show Live Status Tablebooltrueshow_live_table
Live Table PositionstringBottom-Rightlive_table_position
Show Curve Reference Tablebooltrueshow_reference_table
Reference Table PositionstringBottom-Leftreference_table_position
Table Text SizestringSmalltable_text_size
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

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
257 stars

This indicator overlays an intraday SPX and ES chart with a midnight session range, regular-trading-hours opening levels, and a VWAP projection box. It draws the midnight high, mid, and low, flags edge rejections within an absolute tolerance, and plots optiona

Indicator v6 TrendSupport & resistance
Updated 6 weeks ago · None stated GitHub repository of SPX Midnight Box + Snag Logic

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

Session Edge Probabilities

by TraderOracle
61 stars

Session Edge Probabilities tracks eight session-based trading rules using statistical probabilities evaluated on historical chart data. It utilizes a fixed calculation timeframe to evaluate opening ranges, midnight levels, afternoon reversal windows, and price

Indicator v6 Sessions & timeScreeners & dashboards
Updated 9 days ago · None stated GitHub repository of Session Edge Probabilities
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

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