TradingView indicator guides

ta.lowest() in Pine Script: Current and Previous Windows

ta.lowest() returns a rolling minimum, not a prediction of support. This example draws the lowest low of five chart bars, including the current bar. The distinction between that window and the previous window determines whether a breakdown condition can occur.

Inspect the Pine Script

Specify the data and window

For a complete window of valid values, ta.lowest(source, N) returns min(source[0], source[1], …, source[N−1]). The one-argument form ta.lowest(N) uses low. The displayed script fixes low and N = 5; five is this example’s choice, not a default length supplied by the function.

The output retains the source’s units. Lowest close is the minimum closing price; lowest low includes candle lows. Lowest volume is a volume statistic, not a price floor. For valid OHLC in the same window, lowest low cannot exceed lowest close.

The built-in ignores unavailable source values. Do not infer that a returned number proves N valid observations were present. If your specification requires a full contiguous window of valid values, add and test that readiness condition. The reference below adds no such guard.

The code has no input controls. Change its literal call or request explicit source and length inputs before expecting Settings to offer them. Its unused time utilities do not apply a session filter to the plotted calculation.

A close cannot break below its current lowest low

Suppose the five completed lows are [100, 98, 99, 101, 102], oldest to newest. Their minimum is 98. A new candle has low 96 and close 97. The new five-bar window is [98, 99, 101, 102, 96], with minimum 96.

The close of 97 breaks below the previous minimum of 98. It remains above the current minimum of 96. On valid OHLC, ta.lowest(low, N) ≤ low ≤ close. A strict close below the current-window minimum is therefore impossible; equality is a touch, not a strict breakdown.

For a fixed N, ta.lowest(low, N)[1] references the previous completed rolling result. ta.lowest(low[1], N) scans lows one through N bars ago. Calculate the series consistently each bar. Both exclude the developing current low, but their reference window advances at the next bar.

A below-range state is different from the first transition into that state. Specify equality, the previous comparison, chart-bar confirmation and execution timing before testing a breakdown rule. The indicator here implements neither entries nor exits.

A rising minimum need not mean rising recent lows

Consider the five-bar window [90, 97, 96, 95, 94]. Its minimum is 90. The next low is 93. When 90 expires, the window becomes [97, 96, 95, 94, 93] and its minimum rises to 93, even though the newest low fell from 94 to 93. A rising line can be an expiration effect.

For nested windows ending on the same bar, the longer window’s minimum is less than or equal to the shorter window’s minimum. It need not change less often over time. There is no general rule that doubling length halves the number of changes or doubles a signal’s lag.

A current low can move lower before its bar closes. Advancing the window does not itself rewrite earlier closed-bar minima. Revised data, changed settings or a different chart session can change a recalculated series. See TradingView’s repainting documentation.

Price values, offsets and session boundaries are different

ta.lowest() returns a value. ta.lowestbars() returns the location as a bar offset, with historical positions expressed as negative offsets. Negating that offset gives a nonnegative bars-ago distance for history indexing. Neither an older nor a newer minimum establishes support strength by itself.

The ta namespace was introduced in the v5 migration. The official migration table maps lowest to ta.lowest; the namespaced form did not originate in v3.

Length counts chart bars, not elapsed calendar days. Fourteen stock daily bars ordinarily span more than two calendar weeks. A five-bar intraday window can cross a session boundary; shifting the source by one bar does not remove premarket or postmarket data. Use the intended chart session or an explicitly defined session-filtered calculation, then test the first regular-session bar. TradingView’s session documentation describes the relevant distinctions.

Match the symbol, session, timeframe and source when comparing values. A regular-hours and extended-hours series answer different questions; neither is universally the correct trading reference.

A lower-bound candidate is not a complete protective stop

A hypothetical long-stop candidate can subtract an ATR multiple from a rolling low. That construction does not establish a successful stop distance or prevent losses from ordinary fluctuations. A wider stop changes risk per unit, so position sizing and execution need separate decisions.

The candidate may decrease when a new low arrives or ATR increases. For example, low 100 minus 1.5 × ATR 2 gives 97; a rising low of 101 with ATR 4 gives 95. Checking only whether the low rose does not enforce an upward stop ratchet.

A one-way long trailing rule must retain the maximum of the newly calculated candidate and its previously retained stop during that position, then reset deliberately for a new position. A close-based exit differs from an intrabar stop order. Define order timing, gaps, fees, slippage and the fill model before evaluating either.

Inspect ta.highest() for the upper boundary and ATR for range measurement. Neither a channel nor a volatility buffer supplies a measured win rate.

Pine Script v6: implementation and limits

Paste the complete source into Pine Editor and compare its red plot against five candle lows. The script includes the current bar, has no settings inputs or alerts, and places no orders. The supporting time functions are present but unused by the minimum calculation.

Pine Script v6
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify

//======================================================================//
//                    ____  _            _  __                          //
//                   |  _ (_)_ __   ___(_)/ _|_   _                    //
//                   | |_) | | '_  / _  | |_| | | |                   //
//                   |  __/| | | | |  __/ |  _| |_| |                   //
//                   |_|   |_|_| |_|___|_|_|  __, |                   //
//                                             |___/                    //
//======================================================================//

//@version=6
indicator(title="Lowest", overlay=true, max_labels_count=500)

//#region —————————————————————————————————————————————————— Custom Code

//#endregion ————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Common Dependence

p_comm_time_range_to_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int start_unix_time = na
    int end_unix_time = na
    int start_time_hour = na
    int start_time_minute = na
    int end_time_hour = na
    int end_time_minute = na
    if str.length(time_range) == 11
        // Format: hh:mm-hh:mm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
    else if str.length(time_range) == 9
        // Format: hhmm-hhmm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
    start_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)
    end_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)
    [start_unix_time, end_unix_time]

p_comm_time_range_to_start_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int start_time_hour = na
    int start_time_minute = na
    if str.length(time_range) == 11
        // Format: hh:mm-hh:mm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
    else if str.length(time_range) == 9
        // Format: hhmm-hhmm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
    timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)

p_comm_time_range_to_end_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int end_time_hour = na
    int end_time_minute = na
    if str.length(time_range) == 11
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
    else if str.length(time_range) == 9
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
    timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)

p_comm_timeframe_to_seconds(simple string tf) =>
    float seconds = 0
    tf_lower = str.lower(tf)
    value = str.tonumber(str.substring(tf_lower, 0, str.length(tf_lower) - 1))
    if str.endswith(tf_lower, 's')
        seconds := value
    else if str.endswith(tf_lower, 'd')
        seconds := value * 86400
    else if str.endswith(tf_lower, 'w')
        seconds := value * 604800
    else if str.endswith(tf_lower, 'm')
        seconds := value * 2592000
    else
        seconds := str.tonumber(tf_lower) * 60
    seconds

p_custom_sources() =>
    [open, high, low, close, volume]

//#endregion —————————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Ta Dependence


//#endregion —————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Constants

// Input Groups
string P_GP_1      =      ""

//#endregion —————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Inputs

//#endregion ———————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Price Data



//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Indicators

p_ind_1      =      ta.lowest(low, 5) // Lowest


//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Conditions

//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Indicator Plots

// Lowest
plot(p_ind_1, "Lowest", color.rgb(242, 54, 69, 0), 1)

//#endregion ————————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Custom Plots

//#endregion —————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Alert

//#endregion ——————————————————————————————————————————————————————

Existing indicator chart preview
Existing visual preview. Verify exact values against the source; the image is not a performance result.

Specify a checkable adaptation in Pineify

Specify the source, lookback, current versus prior window and session policy. If you need a trailing stop, define persistent position state, reset timing and order execution separately. Request syntax checking, then compare several known windows in TradingView before testing any strategy.

Open Pine Script AI Coding Agent

Educational indicator analysis. No measured trading performance is established here. Syntax validation does not prove formula equivalence, execution assumptions or profitability.

Implementation questions

For an available source value, the current source is the entire window. With low as source, the line is the current low, not a prior support level.

Not in this reference: low and 5 are literals and no inputs are declared. Request input controls or edit the call, then inspect the final code.

No. It identifies equality with a selected rolling minimum. Price can make successive new lows, and a touch provides no measured reversal probability.

No. It shifts the source by one chart bar. Session selection and filtering require separate logic or the intended chart data session.

No. A rolling minimum can occur on the current bar. A pivot rule usually needs later bars for confirmation and becomes known at a different time.

This page contains arithmetic checks, not measured trades. Any performance comparison needs complete entry and exit rules, position size, transaction costs and separate evaluation data.