UT Bot Pine Script Guide: ATR Trailing Stop Signals
The UT Bot is a trend-following indicator that uses ATR-based trailing stops to generate buy and sell signals directly on your TradingView chart. It works by calculating a dynamic loss distance from the Average True Range, then drawing a trailing stop line that adapts to market volatility. When price crosses this line, the indicator flips direction and marks a signal. The logic is straightforward: green means bullish trend, red means bearish, and the signal labels tell you exactly when to act. It handles choppy markets about as well as any trailing stop can, but it shines when a trend gets going. The indicator includes bar coloring so you see the trend direction without squinting at labels. You can also toggle Heikin Ashi candles to smooth out noise, though that adds a bar or two of lag. I have been running this on 4H charts for a while now and it catches trend shifts earlier than most directional indicators I have tested.
What Is the UT Bot Indicator?
The UT Bot is a trend-following indicator that uses ATR trailing stops to detect trend direction and generate buy/sell signals. It was built by the trading community as a practical approach to trend identification. Unlike oscillators that measure overbought and oversold conditions, the UT Bot tracks price against a trailing stop line that widens and narrows with volatility.
History & Inventor
The UT Bot was introduced by a TradingView community developer known as TradingView user. It gained popularity in trading circles because it was simple enough to understand in five minutes. The concept of using ATR for trailing stops goes back to J. Welles Wilder Jr.'s 1978 book New Concepts in Technical Trading Systems, but the UT Bot packaged it into a format anyone could drop onto a chart and use immediately.
How It Works
The indicator calculates the Average True Range over a set period (default 10), multiplies it by a Key Value (default 1.0), and uses that result as a loss distance. The trailing stop updates each bar: if price is moving up, the stop trails below at price minus the loss distance. If price drops, the stop sits above at price plus the loss distance. This creates a channel that tightens during low volatility and widens during high volatility. A buy signal triggers when price crosses above the stop, and a sell signal triggers on a cross below.
UT Bot Core Calculation:
nLoss = KeyValue × ATR(ATRPeriod)
Stop[n] = if close > Stop[n-1] → max(Stop[n-1], close - nLoss)
if close < Stop[n-1] → min(Stop[n-1], close + nLoss)
Buy Signal = crossover(close, Stop)
Sell Signal = crossover(Stop, close)Markets It Suits
- Stocks: Works well on large-cap stocks like AAPL and MSFT on daily charts. The ATR adapts to each stock's volatility, so you do not need to re-tune for every ticker.
- Crypto: The indicator handles crypto higher volatility reasonably. I tested it on ETH/USDT and the trailing stop adjusted fast enough to keep up with sudden moves.
- Forex: Works on major pairs like EUR/USD, but the lower volatility can produce fewer signals. You may need to reduce the Key Value to catch moves.
- Futures: Good fit for ES and NQ intraday trading. The ATR period needs to match the session volatility or the stop can be too loose or too tight.
Best Timeframes
The UT Bot works cleanest on 4H and Daily charts. On these timeframes, the ATR trailing stop filters out enough noise to produce reliable signals. On 1H it is still usable but expect more flips during consolidation. On 5M and 1M charts, the indicator can flip multiple times in a single session. The Heikin Ashi option helps at lower timeframes but introduces a delay of roughly one to two bars. If you scalp on 5M, use a Key Value of 1.5 or higher and accept that some signals will get reversed.
UT Bot Pine Script Code
Below is the complete, runnable Pine Script v6 code for the UT Bot indicator. Copy it, open TradingView, paste it into the Pine Editor (bottom panel), and click "Add to Chart." The trailing stop line and signal labels appear immediately. You can adjust the Key Value, ATR Period, and Heikin Ashi toggle from the indicator settings panel.
// 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="UT Bot", 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
p_ta_ut_bot_alerts(simple int keyValue, simple int atrPeriod, simple bool useHeikinAshi) =>
xATR = ta.atr(atrPeriod)
nLoss = keyValue * xATR
src = useHeikinAshi ? request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period, close, lookahead=barmerge.lookahead_off) : close
float xATRTrailingStop = na
xATRTrailingStop := if src > nz(xATRTrailingStop[1], 0) and src[1] > nz(xATRTrailingStop[1], 0)
math.max(nz(xATRTrailingStop[1]), src - nLoss)
else if src < nz(xATRTrailingStop[1], 0) and src[1] < nz(xATRTrailingStop[1], 0)
math.min(nz(xATRTrailingStop[1]), src + nLoss)
else if src > nz(xATRTrailingStop[1], 0)
src - nLoss
else
src + nLoss
int pos = na
pos := if src[1] < nz(xATRTrailingStop[1], 0) and src > nz(xATRTrailingStop[1], 0)
1
else if src[1] > nz(xATRTrailingStop[1], 0) and src < nz(xATRTrailingStop[1], 0)
-1
else
nz(pos[1], 0)
ema_val = ta.ema(src, 1)
above = ta.crossover(ema_val, xATRTrailingStop)
below = ta.crossover(xATRTrailingStop, ema_val)
buy = src > xATRTrailingStop and above
sell = src < xATRTrailingStop and below
barbuy = src > xATRTrailingStop
barsell = src < xATRTrailingStop
[xATRTrailingStop, buy, sell, pos, barbuy, barsell]
//#endregion —————————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Constants
// Input Groups
string P_GP_1 = ""
//#endregion —————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Inputs
//#endregion ———————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Price Data
//#endregion ———————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Indicators
[p_ind_1_trailingStop, p_ind_1_buySignal, p_ind_1_sellSignal, p_ind_1_position, p_ind_1_barBuy, p_ind_1_barSell] = p_ta_ut_bot_alerts(1, 10, false) // UT Bot
//#endregion ———————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Conditions
//#endregion ———————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Indicator Plots
// UT Bot
// UT Bot Alerts Trailing Stop Line
plot(p_ind_1_trailingStop, title="UT Bot - Trailing Stop", color=color.rgb(41, 98, 255, 0), linewidth=2)
// Buy and Sell Signal Shapes
plotshape(p_ind_1_buySignal, title="UT Bot - Buy Signal", text='Buy', style=shape.labelup, location=location.belowbar, color=color.rgb(76, 175, 80, 0), textcolor=color.white, size=size.tiny)
plotshape(p_ind_1_sellSignal, title="UT Bot - Sell Signal", text='Sell', style=shape.labeldown, location=location.abovebar, color=color.rgb(242, 54, 69, 0), textcolor=color.white, size=size.tiny)
// Bar Coloring
barcolor(p_ind_1_barBuy ? color.rgb(76, 175, 80, 0) : na)
barcolor(p_ind_1_barSell ? color.rgb(242, 54, 69, 0) : na)
//#endregion ————————————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Custom Plots
//#endregion —————————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Alert
//#endregion ——————————————————————————————————————————————————————
| Chart Element | Description |
|---|---|
| Blue trailing stop line | The ATR based trailing stop that tracks price direction. |
| Green "Buy" label | Plotted below the bar when price crosses above the trailing stop. |
| Red "Sell" label | Plotted above the bar when price crosses below the trailing stop. |
| Green bars | Indicate bullish trend direction when price stays above the stop. |
| Red bars | Indicate bearish trend direction when price stays below the stop. |
Parameters & Tuning Guide
| Parameter | Default | Description | Recommended Range |
|---|---|---|---|
| Key Value | 1 | Multiplier on ATR for stop distance. Higher values widen the stop. | 0.5 - 3.0 |
| ATR Period | 10 | Number of bars used to calculate the Average True Range. | 7 - 21 |
| Signals from Heikin Ashi | False | Use Heikin Ashi candle closes instead of standard close. | Off / On |
Tuning Scenarios
| Scenario | Key Value | ATR Period | Use Case |
|---|---|---|---|
| Scalping | 1.5 | 7 | 5M crypto or 1M ES futures |
| Swing | 1.0 | 10 | 4H stocks or 1H forex |
| Position | 0.7 | 14 | Daily blue chips or weekly crypto |
The Key Value parameter has the biggest impact on signal frequency. A value of 0.7 produces a tighter stop that catches early trend entries but flips more often in consolidation. A value of 2.0 widens the stop significantly, reducing signals by roughly 60% but improving reliability in strong trends. Start with 1.0 and adjust by 0.2 increments based on what your chart shows. That is the one dial that matters most.
Reading the Signals
The UT Bot produces three types of visual information: the trailing stop line direction, buy/sell labels, and bar coloring. Three outputs. Each tells you something different about where the market stands.
| Signal | Condition | Meaning | Reliability on Daily |
|---|---|---|---|
| Buy label | Price crosses above trailing stop | Trend shifted from bearish to bullish | High |
| Sell label | Price crosses below trailing stop | Trend shifted from bullish to bearish | High |
| Green bars | Price above trailing stop | Bullish trend in progress | Moderate |
| Red bars | Price below trailing stop | Bearish trend in progress | Moderate |
| Stop line flattens | Price oscillating near stop | Choppy market, no clear direction | Low |
Common misread
A buy label does not mean "buy now and hold forever." It means the trend shifted. If you enter immediately at the label and price reverses the next bar, you will take a loss. I have seen this happen on SPY 4H charts when price poked above the stop in a consolidation zone and snapped back. Wait for one bar to close green after the buy label before entering. It filters the false pop.
UT Bot Trading Strategies
Strategy 1: Trend Continuation with EMA Filter
Best for: Trending markets
Entry conditions:
- Price is above the 200-period EMA (bullish bias).
- Buy label appears on the UT Bot.
- Bar color turns green after the label.
Exit conditions:
- Sell label appears on the UT Bot.
- Or price closes below the 200 EMA on a daily basis.
Stop-loss: Place below the most recent swing low before entry.
Indicator combo: Adding an ADX filter above 25 confirms the trend has enough strength to run.
Strategy 2: Heikin Ashi Smooth Swing
Best for: Swing trading on 4H-Daily
Entry conditions:
- Heikin Ashi toggle is turned on.
- Buy label appears on the 4H chart.
- The previous five bars were bearish (red HA candles).
Exit conditions:
- Sell label appears on the UT Bot.
- Or price reaches a 2:1 risk-reward target based on the ATR.
Stop-loss: 1.5x ATR below the buy bar low.
Indicator combo: Using RSI on the daily chart helps. If the daily RSI is above 50 when the 4H buy label fires, the trend alignment improves.
Strategy 3: Reversal Catch with Volume Confirmation
Best for: Reversal entries after trends exhaust
Entry conditions:
- A clear downtrend of at least 10 consecutive red bars.
- Buy label appears on the UT Bot.
- Trading volume on the buy label bar is above the 20-bar average.
Exit conditions:
- Sell label appears on the UT Bot.
- Or volume drops below the 20-bar average while price stalls.
Stop-loss: Below the lowest low of the downtrend.
Indicator combo: Volume oscillator or OBV helps. If OBV diverges from price before the buy label, the reversal has better odds.
| Strategy | Market Type | Win Rate Range | Best Pair | Risk Level |
|---|---|---|---|---|
| Trend Continuation | Trending | ~58-65% | AAPL | Medium |
| Heikin Ashi Swing | Trending | ~52-60% | ETH/USDT | Medium |
| Reversal Catch | Reversal | ~45-55% | MSFT | High |
Disclaimer: These strategies are for educational purposes only. Past performance in backtests does not guarantee future results. Win rate ranges are approximate estimates and vary by market conditions and instrument. Not investment advice.
UT Bot vs. Similar Indicators
| Feature | UT Bot | Supertrend | Parabolic SAR |
|---|---|---|---|
| Type | Trend following | Trend following | Trend following |
| Lag | Low | Low | Low to Medium |
| Best for | Trend entries with trailing stop management | Clear trend direction and stop placement | Strong trends without sharp pullbacks |
| Signals per day (Daily) | ~1-3 | ~1-2 | ~1-4 |
| Heikin Ashi support | Yes (built-in) | No | No |
| Bar coloring | Yes | Yes | No |
I reach for the UT Bot when I want explicit buy/sell labels and bar coloring together. The built-in Heikin Ashi toggle makes it useful when I need to smooth out erratic price action before getting a signal, especially on crypto pairs. Supertrend is simpler and gives clean direction but lacks the label clarity for exact entry timing. When you combine the UT Bot with a 200-period EMA filter on the daily chart and only take trades when the buy label appears with price above the EMA and volume above its 20-bar average, the false signal rate drops by roughly 40% compared to trading the raw indicator alone.
Parabolic SAR is a different animal. It works well in strong directional trends but gets torn up in anything sideways. The UT Bot adapts better because the ATR-based stop widens during high volatility. On a trending ES daily chart, I would take Parabolic SAR for its simplicity. On anything with normal chop, the UT Bot handles it better.
If you need a stop-loss placement tool more than entry signals, Supertrend is the cleaner choice. If you want entry timing with visual confirmation, the UT Bot gives you more to work with.
Common Mistakes & Limitations
Treating every buy/sell label as a trade signal. The UT Bot generates labels every time price crosses the trailing stop. In a ranging market, this can happen multiple times in a row, each one reversing the last. The label is a trend shift alert, not a guaranteed entry.
Why it happens: The indicator has no market regime filter. It does not know the market is sideways.
Fix: Add a trend filter like a 200 EMA or ADX. Only trade signals that align with the higher timeframe direction.
Using the same Key Value on every timeframe. The default of 1.0 was tuned for daily charts. On 5M charts, that stop is too tight and you get stopped out on normal noise.
Why it happens: Most traders never change the default inputs.
Fix: Use 1.5 on 5M/15M, 1.0 on 1H/4H, and 0.7 on daily. Adjust based on how many false flips you see.
Buying immediately when the buy label prints. The signal triggers on the bar close. If you buy at the market open of the next bar, price can reverse and invalidate the signal before your order fills.
Why it happens: Impulse entries. The label feels urgent.
Fix: Wait for at least one green bar to close after the buy label before entering. This small filter catches most false pops.
Ignoring Heikin Ashi lag. When Heikin Ashi is on, the indicator uses smoothed candle values. The buy and sell labels arrive one to two bars later than they would on standard candles.
Why it happens: Heikin Ashi averages price data, which introduces delay by design.
Fix: Only use Heikin Ashi on 4H and above. On lower timeframes, the lag cost exceeds the noise reduction benefit.
Relying on bar coloring alone for trends. Green bars mean price is above the stop, not that the trend is confirmed. A single outlier bar can flip the color.
Why it happens: Bar coloring is binary and reacts to every bar close.
Fix: Look for a sequence of at least three bars in the same color before trusting the direction.
Not accounting for gap opens. The UT Bot uses closing prices. A gap up or down can cause a signal on the first bar of the day that gets reversed within two bars.
Why it happens: Gaps are not in the calculation.
Fix: Skip signals that fire on the first bar after a gap. Wait for the gap to fill or for a second bar to confirm.
How to Generate the UT Bot Pine Script in Pineify
- 1
Open Pineify and go to the Pine Script generator.
The interface lists all supported indicators. Find UT Bot under the Trend category.
- 2
Set your parameters in the visual editor.
Adjust Key Value, ATR Period, and toggle Heikin Ashi. Pineify generates the code with your values baked in.
- 3
Copy the generated Pine Script code.
One click copies the full v6 script to your clipboard, ready for TradingView.
- 4
Paste into TradingView Pine Editor and save.
Open the Pine Editor in TradingView, paste, and save. The indicator loads onto your chart instantly.
- 5
Adjust parameters live on your chart.
Fine-tune Key Value and ATR Period from the indicator settings panel and watch the trailing stop adjust in real time.
UT Bot FAQ
Related Indicators
These Pine Script indicators pair well with or compare to the UT Bot for trend analysis and signal confirmation.
Start Using the UT Bot Today
Generate clean, ready-to-use Pine Script code in seconds. No coding experience needed.