//@version=6
indicator("CANDLE_TIME_RD", overlay = true, max_labels_count = 500)
// This tool displays the time of each candle directly on the chart by placing a label below
// the bar with an upward-pointing arrow for clear visual alignment. It helps traders quickly
// identify the exact timestamp of any candle during fast intraday analysis or historical review.
//
// OVERVIEW
// The script adjusts candle times relative to EST using a user-controlled timezone offset.
// It formats the timestamp in AM/PM or military style and can optionally display it vertically
// by stacking each character on its own line. Labels appear based on minute modulo logic for
// clean spacing so the chart does not become overloaded.
//
// FEATURES
// - Timezone offset input relative to EST (example: CST = -1, PST = -3).
// - AM/PM or military time toggle.
// - Vertical text toggle (on by default) that prints HH on first line and MM on second.
// - Modulo-based spacing so labels appear every N-th minute.
// - Clean label placement below candles with an upward-pointing arrow.
// - Manual string construction for hours and minutes to keep formatting consistent.
//
// USE CASES
// - Reviewing setups with ChatGPT where exact candle timing matters.
// - Syncing TradingView data to a chosen timezone for journaling.
// - Studying EMA touches, VWAP interactions, or momentum shifts at specific times.
// - Reducing clutter by showing timestamps at controlled intervals.
//
// Designed for clarity and fast intraday evaluation.
// Inputs
step = input.int(5, "Label every N-th minute (modulo)", minval = 1)
useMilitary = input.bool(false, "Use Military Time?")
tzOffset = input.int(0, "Timezone Offset from EST")
useVertical = input.bool(true, "Display text vertically (HH on top, MM below)")
minuteOnly = input.bool(false, "Print minute only?")
bubbleOpacity = input.int(80, "Bubble Transparency (0 = solid, 100 = invisible)", minval = 0, maxval = 100)
vertOffsetTicks = input.int(1, "Vertical offset (ticks below low)", minval = 0)
labelSize = input.string("small", "Label Size", options = ["tiny", "small", "normal", "large", "huge"])
// Time adjustment
offsetMs = tzOffset * 60 * 60 * 1000
adjTime = time + offsetMs
// Hour and minute
h = hour(adjTime)
m = minute(adjTime)
// Format minute with leading zero
mStr = m < 10 ? "0" + str.tostring(m) : str.tostring(m)
// Format hours WITH leading zero
h24Str = h < 10 ? "0" + str.tostring(h) : str.tostring(h)
h12 = h % 12
h12 := h12 == 0 ? 12 : h12
h12StrRaw = str.tostring(h12)
h12Str = h12 < 10 ? "0" + h12StrRaw : h12StrRaw
ampm = h >= 12 ? " PM" : " AM"
// Base time string
timeText = useMilitary ? h24Str + ":" + mStr : h12Str + ":" + mStr + ampm
if minuteOnly
timeText := mStr
// Vertical formatting: print as HH\nMM
finalText = timeText
if useVertical
if minuteOnly
finalText := mStr
else
hourPart = useMilitary ? h24Str : h12Str
finalText := hourPart + "\n" + mStr
// Label placement
labelPrice = low - vertOffsetTicks * syminfo.mintick
// Convert label size string to enum
sz = labelSize == "tiny" ? size.tiny : labelSize == "small" ? size.small : labelSize == "normal" ? size.normal : labelSize == "large" ? size.large : size.huge
// Draw label (one-liner)
if barstate.isnew and m % step == 0
label.new(bar_index, labelPrice, finalText, xloc = xloc.bar_index, yloc = yloc.price, style = label.style_label_up, textcolor = color.white, color = color.new(color.black, bubbleOpacity), size = sz)